/test-string-list
/test-subprocess
/test-svn-fe
+/test-urlmatch-normalization
/test-wildmatch
/common-cmds.h
*.tar.gz
* "git diff --no-index" did not work with pagers correctly.
* "git diff COPYING HEAD:COPYING" gave a nonsense error message that
- claimed that the treeish HEAD did not have COPYING in it.
+ claimed that the tree-ish HEAD did not have COPYING in it.
* When "git log" gets "--simplify-merges/by-decoration" together with
"--first-parent", the combination of these options makes the
--- /dev/null
+Git v1.8.4.1 Release Notes
+========================
+
+Fixes since v1.8.4
+------------------
+
+ * Some people still use rather old versions of bash, which cannot
+ grok some constructs like 'printf -v varname' the prompt and
+ completion code started to use recently. The completion and
+ prompt scripts have been adjusted to work better with these old
+ versions of bash.
+
+ * "git rebase -i" had a minor bug (the same could be in other
+ programs, as the root cause is pretty generic) where the code
+ feeds a random, data dependeant string to 'echo' and expects it
+ to come out literally.
+
+ * "submodule.<name>.path" variable mistakenly set to the empty
+ "true" caused the configuration parser to segfault.
+
+ * Output from "git log --full-diff -- <pathspec>" looked strange,
+ because comparison was done with the previous ancestor that
+ touched the specified <pathspec>, causing the patches for paths
+ outside the pathspec to show more than the single commit has
+ changed.
+
+ * The auto-tag-following code in "git fetch" tries to reuse the
+ same transport twice when the serving end does not cooperate and
+ does not give tags that point to commits that are asked for as
+ part of the primary transfer. Unfortunately, Git-aware transport
+ helper interface is not designed to be used more than once, hence
+ this did not work over smart-http transfer. Fixed.
+
+ * Send a large request to read(2)/write(2) as a smaller but still
+ reasonably large chunks, which would improve the latency when the
+ operation needs to be killed and incidentally works around broken
+ 64-bit systems that cannot take a 2GB write or read in one go.
+
+ * A ".mailmap" file that ends with an incomplete line, when read
+ from a blob, was not handled properly.
+
+ * The recent "short-cut clone connectivity check" topic broke a
+ shallow repository when a fetch operation tries to auto-follow
+ tags.
+
+ * On platforms with fgetc() and friends defined as macros,
+ the configuration parser did not compile.
+
+Also contains a handful of trivial code clean-ups, documentation
+updates, updates to the test suite, etc.
Foreign interfaces, subsystems and ports.
+ * "git-svn" used with SVN 1.8.0 when talking over https:// connection
+ dumped core due to a bug in the serf library that SVN uses. Work
+ it around on our side, even though the SVN side is being fixed.
+
+ * On MacOS X, we detected if the filesystem needs the "pre-composed
+ unicode strings" workaround, but did not automatically enable it.
+ Now we do.
+
* remote-hg remote helper misbehaved when interacting with a local Hg
repository relative to the home directory, e.g. "clone hg::~/there".
UI, Workflows & Features
+ * A packfile that stores the same object more than once is broken and
+ will be rejected by "git index-pack" that is run when receiving
+ data over the wire.
+
+ * Earlier we started rejecting an attempt to add 0{40} object name to
+ the index and to tree objects, but it sometimes is necessary to
+ allow so to be able to use tools like filter-branch to correct such
+ broken tree objects. "filter-branch" can again be used to to do
+ so.
+
+ * "git config" did not provide a way to set or access numbers larger
+ than a native "int" on the platform; it now provides 64-bit signed
+ integers on all platforms.
+
+ * "git pull --rebase" always chose to do the bog-standard flattening
+ rebase. You can tell it to run "rebase --preserve-merges" by
+ setting "pull.rebase" configuration to "preserve".
+
+ * "git push --no-thin" actually disables the "thin pack transfer"
+ optimization.
+
+ * Magic pathspecs like ":(icase)makefile" that matches both
+ Makefile and makefile can be used in more places.
+
+ * The "http.*" variables can now be specified per URL that the
+ configuration applies. For example,
+
+ [http]
+ sslVerify = true
+ [http "https://weak.example.com/"]
+ sslVerify = false
+
+ would flip http.sslVerify off only when talking to that specified
+ site.
+
+ * "git mv A B" when moving a submodule A has been taught to
+ relocate its working tree and to adjust the paths in the
+ .gitmodules file.
+
+ * "git blame" can now take more than one -L option to discover the
+ origin of multiple blocks of the lines.
+
+ * The http transport clients can optionally ask to save cookies
+ with http.savecookies configuration variable.
+
+ * "git push" learned a more fine grained control over a blunt
+ "--force" when requesting a non-fast-forward update with the
+ "--force-with-lease=<refname>:<expected object name>" option.
+
+ * "git diff --diff-filter=<classes of changes>" can now take
+ lowercase letters (e.g. "--diff-filter=d") to mean "show
+ everything but these classes". "git diff-files -q" is now a
+ deprecated synonym for "git diff-files --diff-filter=d".
+
+ * "git fetch" (hence "git pull" as well) learned to check
+ "fetch.prune" and "remote.*.prune" configuration variables and
+ to behave as if the "--prune" command line option was given.
+
* "git check-ignore -z" applied the NUL termination to both its input
(with --stdin) and its output, but "git check-attr -z" ignored the
option on the output side. Make both honor -z on the input and
Performance, Internal Implementation, etc.
+ * If a build-time fallback is set to "cat" instead of "less", we
+ should apply the same "no subprocess or pipe" optimization as we
+ apply to user-supplied GIT_PAGER=cat.
+
* Many commands use --dashed-option as a operation mode selector
(e.g. "git tag --delete") that the user can use at most one
(e.g. "git tag --delete --verify" is a nonsense) and you cannot
track are contained in this release (see release notes to them for
details).
+ * "git cvsserver" computed the permission mode bits incorrectly for
+ executable files.
+ (merge 1b48d56 jc/cvsserver-perm-bit-fix later to maint).
+
+ * When send-email comes up with an error message to die with upon
+ failure to start an SSL session, it tried to read the error string
+ from a wrong place.
+ (merge 6cb0c88 bc/send-email-ssl-die-message-fix later to maint).
+
+ * The implementation of "add -i" has a crippling code to work around
+ ActiveState Perl limitation but it by mistake also triggered on Git
+ for Windows where MSYS perl is used.
+ (merge df17e77 js/add-i-mingw later to maint).
+
+ * We made sure that we notice the user-supplied GIT_DIR is actually a
+ gitfile, but did not do the same when the default ".git" is a
+ gitfile.
+ (merge 487a2b7 nd/git-dir-pointing-at-gitfile later to maint).
+
+ * When an object is not found after checking the packfiles and then
+ loose object directory, read_sha1_file() re-checks the packfiles to
+ prevent racing with a concurrent repacker; teach the same logic to
+ has_sha1_file().
+ (merge 45e8a74 jk/has-sha1-file-retry-packed later to maint).
+
+ * "git commit --author=$name", when $name is not in the canonical
+ "A. U. Thor <au.thor@example.xz>" format, looks for a matching name
+ from existing history, but did not consult mailmap to grab the
+ preferred author name.
+ (merge ea16794 ap/commit-author-mailmap later to maint).
+
+ * "git ls-files -k" needs to crawl only the part of the working tree
+ that may overlap the paths in the index to find killed files, but
+ shared code with the logic to find all the untracked files, which
+ made it unnecessarily inefficient.
+ (merge 680be04 jc/ls-files-killed-optim later to maint).
+
+ * The commit object names in the insn sheet that was prepared at the
+ beginning of "rebase -i" session can become ambiguous as the
+ rebasing progresses and the repository gains more commits. Make
+ sure the internal record is kept with full 40-hex object names.
+ (merge 75c6976 es/rebase-i-no-abbrev later to maint).
+
+ * "git rebase --preserve-merges" internally used the merge machinery
+ and as a side effect, left merge summary message in the log, but
+ when rebasing, there should not be a need for merge summary.
+ (merge a9f739c rt/rebase-p-no-merge-summary later to maint).
+
+ * A call to xread() was used without a loop around to cope with short
+ read in the codepath to stream new contents to a pack.
+ (merge e92527c js/xread-in-full later to maint).
+
+ * "git rebase -i" forgot that the comment character can be
+ configurable while reading its insn sheet.
+ (merge 7bca7af es/rebase-i-respect-core-commentchar later to maint).
+
+ * The mailmap support code read past the allocated buffer when the
+ mailmap file ended with an incomplete line.
+ (merge f972a16 jk/mailmap-incomplete-line later to maint).
+
+ * We used to send a large request to read(2)/write(2) as a single
+ system call, which was bad from the latency point of view when
+ the operation needs to be killed, and also triggered an error on
+ broken 64-bit systems that refuse to take more than 2GB read or
+ write in one go.
+ (merge a487916 sp/clip-read-write-to-8mb later to maint).
+
+ * "git fetch" that auto-followed tags incorrectly reused the
+ connection with Git-aware transport helper (like the sample "ext::"
+ helper shipped with Git).
+ (merge 0f73f8b jc/transport-do-not-use-connect-twice-in-fetch later to maint).
+
+ * "git log --full-diff -- <pathspec>" showed a huge diff for paths
+ outside the given <pathspec> for each commit, instead of showing
+ the change relative to the parent of the commit. "git reflog -p"
+ had a similar problem.
+ (merge 838f9a1 tr/log-full-diff-keep-true-parents later to maint).
+
* Setting submodule.*.path configuration variable to true (without
giving "= value") caused Git to segfault.
(merge 4b05440 jl/some-submodule-config-are-not-boolean later to maint).
-L <start>,<end>::
-L :<regex>::
- Annotate only the given line range. <start> and <end> are optional.
- ``-L <start>'' or ``-L <start>,'' spans from <start> to end of file.
- ``-L ,<end>'' spans from start of file to <end>.
+ Annotate only the given line range. May be specified multiple times.
+ Overlapping ranges are allowed.
++
+<start> and <end> are optional. ``-L <start>'' or ``-L <start>,'' spans from
+<start> to end of file. ``-L ,<end>'' spans from start of file to <end>.
+
-<start> and <end> can take one of these forms:
-
include::line-range-format.txt[]
-l::
pushNeedsForce::
Shown when linkgit:git-push[1] rejects an update that
tries to overwrite a remote ref that points at an
- object that is not a committish, or make the remote
- ref point at an object that is not a committish.
+ object that is not a commit-ish, or make the remote
+ ref point at an object that is not a commit-ish.
statusHints::
Show directions on how to proceed from the current
state in the output of linkgit:git-status[1], in
When not configured the default commit message editor is used instead.
core.pager::
- The command that Git will use to paginate output. Can
- be overridden with the `GIT_PAGER` environment
- variable. Note that Git sets the `LESS` environment
- variable to `FRSX` if it is unset when it runs the
- pager. One can change these settings by setting the
- `LESS` variable to some other value. Alternately,
- these settings can be overridden on a project or
- global basis by setting the `core.pager` option.
- Setting `core.pager` has no effect on the `LESS`
- environment variable behaviour above, so if you want
- to override Git's default settings this way, you need
- to be explicit. For example, to disable the S option
- in a backward compatible manner, set `core.pager`
- to `less -+S`. This will be passed to the shell by
- Git, which will translate the final command to
- `LESS=FRSX less -+S`.
+ Text viewer for use by Git commands (e.g., 'less'). The value
+ is meant to be interpreted by the shell. The order of preference
+ is the `$GIT_PAGER` environment variable, then `core.pager`
+ configuration, then `$PAGER`, and then the default chosen at
+ compile time (usually 'less').
++
+When the `LESS` environment variable is unset, Git sets it to `FRSX`
+(if `LESS` environment variable is set, Git does not change it at
+all). If you want to selectively override Git's default setting
+for `LESS`, you can set `core.pager` to e.g. `less -+S`. This will
+be passed to the shell by Git, which will translate the final
+command to `LESS=FRSX less -+S`. The environment tells the command
+to set the `S` option to chop long lines but the command line
+resets it to the default to fold long lines.
core.whitespace::
A comma separated list of common whitespace problems to
instead of merging the default branch from the default remote when
"git pull" is run. See "pull.rebase" for doing this in a non
branch-specific manner.
++
+ When preserve, also pass `--preserve-merges` along to 'git rebase'
+ so that locally committed merge commits will not be flattened
+ by running 'git pull'.
+
*NOTE*: this is a possibly dangerous operation; do *not* use
it unless you understand the implications (see linkgit:git-rebase[1]
working repository in gitweb (see linkgit:git-instaweb[1]).
clean.requireForce::
- A boolean to make git-clean do nothing unless given -f
- or -n. Defaults to true.
+ A boolean to make git-clean do nothing unless given -f,
+ -i or -n. Defaults to true.
color.branch::
A boolean to enable/disable color in the output of
especially on slow filesystems. If not set, the value of
`transfer.unpackLimit` is used instead.
+fetch.prune::
+ If true, fetch will automatically behave as if the `--prune`
+ option was given on the command line. See also `remote.<name>.prune`.
+
format.attach::
Enable multipart/mixed attachments as the default for
'format-patch'. The value can also be a double quoted string
of the file to read cookies from should be plain HTTP headers or
the Netscape/Mozilla cookie file format (see linkgit:curl[1]).
NOTE that the file specified with http.cookiefile is only used as
- input. No cookies will be stored in the file.
+ input unless http.saveCookies is set.
+
+http.savecookies::
+ If set, store cookies received during requests to the file specified by
+ http.cookiefile. Has no effect if http.cookiefile is unset.
http.sslVerify::
Whether to verify the SSL certificate when fetching or pushing
of common USER_AGENT strings (but not including those like git/1.7.1).
Can be overridden by the 'GIT_HTTP_USER_AGENT' environment variable.
+http.<url>.*::
+ Any of the http.* options above can be applied selectively to some urls.
+ For a config key to match a URL, each element of the config key is
+ compared to that of the URL, in the following order:
++
+--
+. Scheme (e.g., `https` in `https://example.com/`). This field
+ must match exactly between the config key and the URL.
+
+. Host/domain name (e.g., `example.com` in `https://example.com/`).
+ This field must match exactly between the config key and the URL.
+
+. Port number (e.g., `8080` in `http://example.com:8080/`).
+ This field must match exactly between the config key and the URL.
+ Omitted port numbers are automatically converted to the correct
+ default for the scheme before matching.
+
+. Path (e.g., `repo.git` in `https://example.com/repo.git`). The
+ path field of the config key must match the path field of the URL
+ either exactly or as a prefix of slash-delimited path elements. This means
+ a config key with path `foo/` matches URL path `foo/bar`. A prefix can only
+ match on a slash (`/`) boundary. Longer matches take precedence (so a config
+ key with path `foo/bar` is a better match to URL path `foo/bar` than a config
+ key with just path `foo/`).
+
+. User name (e.g., `user` in `https://user@example.com/repo.git`). If
+ the config key has a user name it must match the user name in the
+ URL exactly. If the config key does not have a user name, that
+ config key will match a URL with any user name (including none),
+ but at a lower precedence than a config key with a user name.
+--
++
+The list above is ordered by decreasing precedence; a URL that matches
+a config key's path is preferred to one that matches its user name. For example,
+if the URL is `https://user@example.com/foo/bar` a config key match of
+`https://example.com/foo` will be preferred over a config key match of
+`https://user@example.com`.
++
+All URLs are normalized before attempting any matching (the password part,
+if embedded in the URL, is always ignored for matching purposes) so that
+equivalent urls that are simply spelled differently will match properly.
+Environment variable settings always override any matches. The urls that are
+matched against are those given directly to Git commands. This means any URLs
+visited as a result of a redirection do not participate in matching.
+
i18n.commitEncoding::
Character encoding the commit messages are stored in; Git itself
does not care per se, but this information is necessary e.g. when
of merging the default branch from the default remote when "git
pull" is run. See "branch.<name>.rebase" for setting this on a
per-branch basis.
++
+ When preserve, also pass `--preserve-merges` along to 'git rebase'
+ so that locally committed merge commits will not be flattened
+ by running 'git pull'.
+
*NOTE*: this is a possibly dangerous operation; do *not* use
it unless you understand the implications (see linkgit:git-rebase[1]
Setting this to a value <vcs> will cause Git to interact with
the remote with the git-remote-<vcs> helper.
+remote.<name>.prune::
+ When set to true, fetching from this remote by default will also
+ remove any remote-tracking branches which no longer exist on the
+ remote (as if the `--prune` option was give on the command line).
+ Overrides `fetch.prune` settings, if any.
+
remotes.<group>::
The list of remotes which are fetched by "git remote update
<group>". See linkgit:git-remote[1].
Set to true to enable --branch by default in linkgit:git-status[1].
The option --no-branch takes precedence over this variable.
+status.displayCommentPrefix::
+ If set to true, linkgit:git-status[1] will insert a comment
+ prefix before each output line (starting with
+ `core.commentChar`, i.e. `#` by default). This was the
+ behavior of linkgit:git-status[1] in Git 1.8.4 and previous.
+ Defaults to false.
+
status.showUntrackedFiles::
By default, linkgit:git-status[1] and linkgit:git-commit[1] show
files which are not currently tracked by Git. Directories which
* linkgit:git-shell[1] can be used as a 'restricted login shell'
for shared central repository users.
-link:howto/update-hook-example.txt[update hook howto] has a good
+link:howto/update-hook-example.html[update hook howto] has a good
example of managing a shared central repository.
--------
[verse]
'git blame' [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-e] [-p] [-w] [--incremental]
- [-L n,m | -L :fn] [-S <revs-file>] [-M] [-C] [-C] [-C] [--since=<date>]
+ [-L <range>] [-S <revs-file>] [-M] [-C] [-C] [-C] [--since=<date>]
[--abbrev=<n>] [<rev> | --contents <file> | --reverse <rev>] [--] <file>
DESCRIPTION
Annotates each line in the given file with information from the revision which
last modified the line. Optionally, start annotating from the given revision.
-The command can also limit the range of lines annotated.
+When specified one or more times, `-L` restricts annotation to the requested
+lines.
The origin of lines is automatically followed across whole-file
renames (currently there is no option to turn the rename-following
Unlike 'git blame' and 'git annotate' in older versions of git, the extent
of the annotation can be limited to both line ranges and revision
-ranges. When you are interested in finding the origin for
+ranges. The `-L` option, which limits annotation to a range of lines, may be
+specified multiple times.
+
+When you are interested in finding the origin for
lines 40-60 for file `foo`, you can use the `-L` option like so
(they mean the same thing -- both ask for 21 lines starting at
line 40):
new branch.
When a local branch is started off a remote-tracking branch, Git sets up the
-branch so that 'git pull' will appropriately merge from
+branch (specifically the `branch.<name>.remote` and `branch.<name>.merge`
+configuration entries) so that 'git pull' will appropriately merge from
the remote-tracking branch. This behavior may be changed via the global
`branch.autosetupmerge` configuration flag. That setting can be
overridden by using the `--track` and `--no-track` options, and
-t::
--track::
- When creating a new branch, set up configuration to mark the
+ When creating a new branch, set up `branch.<name>.remote` and
+ `branch.<name>.merge` configuration entries to mark the
start-point branch as "upstream" from the new branch. This
configuration will tell git to show the relationship between the
two branches in `git status` and `git branch -v`. Furthermore,
--textconv::
Show the content as transformed by a textconv filter. In this case,
- <object> has be of the form <treeish>:<path>, or :<path> in order
+ <object> has be of the form <tree-ish>:<path>, or :<path> in order
to apply the filter to the content recorded in the index at <path>.
--batch::
------------
If `--batch` or `--batch-check` is given, `cat-file` will read objects
-from stdin, one per line, and print information about them.
-
-Each line is considered as a whole object name, and is parsed as if
-given to linkgit:git-rev-parse[1].
+from stdin, one per line, and print information about them. By default,
+the whole line is considered as an object, as if it were fed to
+linkgit:git-rev-parse[1].
You can specify the information shown for each object by using a custom
`<format>`. The `<format>` is copied literally to stdout for each
The size, in bytes, that the object takes up on disk. See the
note about on-disk sizes in the `CAVEATS` section below.
+`rest`::
+ If this atom is used in the output string, input lines are split
+ at the first whitespace boundary. All characters before that
+ whitespace are considered to be the object name; characters
+ after that first run of whitespace (i.e., the "rest" of the
+ line) are output in place of the `%(rest)` atom.
+
If no format is specified, the default format is `%(objectname)
%(objecttype) %(objectsize)`.
'git config' [<file-option>] [type] [-z|--null] --get name [value_regex]
'git config' [<file-option>] [type] [-z|--null] --get-all name [value_regex]
'git config' [<file-option>] [type] [-z|--null] --get-regexp name_regex [value_regex]
+'git config' [<file-option>] [type] [-z|--null] --get-urlmatch name URL
'git config' [<file-option>] --unset name [value_regex]
'git config' [<file-option>] --unset-all name [value_regex]
'git config' [<file-option>] --rename-section old_name new_name
in which section and variable names are lowercased, but subsection
names are not.
+--get-urlmatch name URL::
+ When given a two-part name section.key, the value for
+ section.<url>.key whose <url> part matches the best to the
+ given URL is returned (if no such key exists, the value for
+ section.key is used as a fallback). When given just the
+ section as name, do so for all the keys in the section and
+ list them.
+
--global::
For writing options: write to global `~/.gitconfig` file
rather than the repository `.git/config`, write to
gitproxy=proxy-command for kernel.org
gitproxy=default-proxy ; for all the rest
+ ; HTTP
+ [http]
+ sslVerify
+ [http "https://weak.example.com"]
+ sslVerify = false
+ cookieFile = /tmp/cookie.txt
+
you can set the filemode to true with
------------
echo "${WS}your whitespace color or blue reverse${RESET}"
------------
+For URLs in `https://weak.example.com`, `http.sslVerify` is set to
+false, while it is set to `true` for all others:
+
+------------
+% git config --bool --get-urlmatch http.sslverify https://good.example.com
+true
+% git config --bool --get-urlmatch http.sslverify https://weak.example.com
+false
+% git config --get-urlmatch http https://weak.example.com
+http.cookiefile /tmp/cookie.txt
+http.sslverify false
+------------
+
include::config.txt[]
GIT
interface to scripts which may want to retrieve, store, or prompt for
credentials in the same manner as Git. The design of this scriptable
interface models the internal C API; see
-link:technical/api-credentials.txt[the Git credential API] for more
+link:technical/api-credentials.html[the Git credential API] for more
background on the concepts.
git-credential takes an "action" option on the command-line (one of
SYNOPSIS
--------
[verse]
-'git describe' [--all] [--tags] [--contains] [--abbrev=<n>] <committish>...
+'git describe' [--all] [--tags] [--contains] [--abbrev=<n>] <commit-ish>...
'git describe' [--all] [--tags] [--contains] [--abbrev=<n>] --dirty[=<mark>]
DESCRIPTION
OPTIONS
-------
-<committish>...::
- Committish object names to describe.
+<commit-ish>...::
+ Commit-ish object names to describe.
--dirty[=<mark>]::
Describe the working tree.
--candidates=<n>::
Instead of considering only the 10 most recent tags as
- candidates to describe the input committish consider
+ candidates to describe the input commit-ish consider
up to <n> candidates. Increasing <n> above 10 will take
slightly longer but may produce a more accurate result.
An <n> of 0 will cause only exact matches to be output.
SEARCH STRATEGY
---------------
-For each committish supplied, 'git describe' will first look for
+For each commit-ish supplied, 'git describe' will first look for
a tag which tags exactly that commit. Annotated tags will always
be preferred over lightweight tags, and tags with newer dates will
always be preferred over tags with older dates. If an exact match
If an exact match was not found, 'git describe' will walk back
through the commit history to locate an ancestor commit which
has been tagged. The ancestor's tag will be output along with an
-abbreviation of the input committish's SHA-1. If '--first-parent' was
+abbreviation of the input commit-ish's SHA-1. If '--first-parent' was
specified then the walk will only consider the first parent of each
commit.
If multiple tags were found during the walk then the tag which
-has the fewest commits different from the input committish will be
+has the fewest commits different from the input commit-ish will be
selected and output. Here fewest commits different is defined as
the number of commits which would be shown by `git log tag..input`
will be the smallest number of commits possible.
words, the differences are what you _could_ tell Git to
further add to the index but you still haven't. You can
stage these changes by using linkgit:git-add[1].
-+
-If exactly two paths are given and at least one points outside
-the current repository, 'git diff' will compare the two files /
-directories. This behavior can be forced by --no-index.
+
+'git diff' --no-index [--options] [--] [<path>...]::
+
+ This form is to compare the given two paths on the
+ filesystem. You can omit the `--no-index` option when
+ running the command in a working tree controlled by Git and
+ at least one of the paths points outside the working tree,
+ or when running the command outside a working tree
+ controlled by Git.
'git diff' [--options] --cached [<commit>] [--] [<path>...]::
`--cat-blob-fd` or `stdout` if unspecified.
`feature`::
- Require that fast-import supports the specified feature, or
- abort if it does not.
+ Enable the specified feature. This requires that fast-import
+ supports the specified feature, and aborts if it does not.
`option`::
Specify any of the options listed under OPTIONS that do not
('author' (SP <name>)? SP LT <email> GT SP <when> LF)?
'committer' (SP <name>)? SP LT <email> GT SP <when> LF
data
- ('from' SP <committish> LF)?
- ('merge' SP <committish> LF)?
+ ('from' SP <commit-ish> LF)?
+ ('merge' SP <commit-ish> LF)?
(filemodify | filedelete | filecopy | filerename | filedeleteall | notemodify)*
LF?
....
be the first ancestor of the new commit.
As `LF` is not valid in a Git refname or SHA-1 expression, no
-quoting or escaping syntax is supported within `<committish>`.
+quoting or escaping syntax is supported within `<commit-ish>`.
-Here `<committish>` is any of the following:
+Here `<commit-ish>` is any of the following:
* The name of an existing branch already in fast-import's internal branch
table. If fast-import doesn't know the name, it's treated as a SHA-1
it is suggested that frontends do not use more than 15 `merge`
commands per commit; 16, if starting a new, empty branch.
-Here `<committish>` is any of the commit specification expressions
+Here `<commit-ish>` is any of the commit specification expressions
also accepted by `from` (see above).
`filemodify`
`notemodify`
^^^^^^^^^^^^
Included in a `commit` `<notes_ref>` command to add a new note
-annotating a `<committish>` or change this annotation contents.
-Internally it is similar to filemodify 100644 on `<committish>`
+annotating a `<commit-ish>` or change this annotation contents.
+Internally it is similar to filemodify 100644 on `<commit-ish>`
path (maybe split into subdirectories). It's not advised to
use any other commands to write to the `<notes_ref>` tree except
`filedeleteall` to delete all existing notes in this tree.
commit that is to be annotated.
+
....
- 'N' SP <dataref> SP <committish> LF
+ 'N' SP <dataref> SP <commit-ish> LF
....
+
Here `<dataref>` can be either a mark reference (`:<idnum>`)
command.
+
....
- 'N' SP 'inline' SP <committish> LF
+ 'N' SP 'inline' SP <commit-ish> LF
data
....
+
See below for a detailed description of the `data` command.
-In both formats `<committish>` is any of the commit specification
+In both formats `<commit-ish>` is any of the commit specification
expressions also accepted by `from` (see above).
`mark`
....
'tag' SP <name> LF
- 'from' SP <committish> LF
+ 'from' SP <commit-ish> LF
'tagger' (SP <name>)? SP LT <email> GT SP <when> LF
data
....
....
'reset' SP <ref> LF
- ('from' SP <committish> LF)?
+ ('from' SP <commit-ish> LF)?
LF?
....
-For a detailed description of `<ref>` and `<committish>` see above
+For a detailed description of `<ref>` and `<commit-ish>` see above
under `commit` and `from`.
The `LF` after the command is optional (it used to be required).
--no-progress::
Do not show the progress.
+--check-self-contained-and-connected::
+ Output "connectivity-ok" if the received pack is
+ self-contained and connected.
+
-v::
Run verbosely.
Note that only message is considered, if also a diff is shown
its size is not included.
--L <start>,<end>:<file>, -L :<regex>:<file>::
+-L <start>,<end>:<file>::
+-L :<regex>:<file>::
Trace the evolution of the line range given by "<start>,<end>"
(or the funcname regex <regex>) within the <file>. You may
give zero or one positive revision arguments.
You can specify this option more than once.
+
-<start> and <end> can take one of these forms:
-
include::line-range-format.txt[]
<revision range>::
DESCRIPTION
-----------
-Reads three treeish, and output trivial merge results and
+Reads three tree-ish, and output trivial merge results and
conflicting stages to the standard output. This is similar to
what three-way 'git read-tree -m' does, but instead of storing the
results in the index, the command outputs the entries to the
to `git merge`, or pass `--ff-only` when you do not have any work on
your own. e.g.
----
+----
git fetch origin
git merge v1.2.3^0
git merge --ff-only v1.2.3
----
+----
HOW CONFLICTS ARE PRESENTED
DESCRIPTION
-----------
-This script is used to move or rename a file, directory or symlink.
+Move or rename a file, directory or symlink.
git mv [-v] [-f] [-n] [-k] <source> <destination>
git mv [-v] [-f] [-n] [-k] <source> ... <destination directory>
--verbose::
Report the names of files as they are moved.
+SUBMODULES
+----------
+Moving a submodule using a gitfile (which means they were cloned
+with a Git version 1.7.8 or newer) will update the gitfile and
+core.worktree setting to make the submodule work in the new location.
+It also will attempt to update the submodule.<name>.path setting in
+the linkgit:gitmodules[5] file and stage that file (unless -n is used).
+
GIT
---
Part of the linkgit:git[1] suite
--------
[verse]
'git name-rev' [--tags] [--refs=<pattern>]
- ( --all | --stdin | <committish>... )
+ ( --all | --stdin | <commit-ish>... )
DESCRIPTION
-----------
:git-pull: 1
-r::
---rebase::
- Rebase the current branch on top of the upstream branch after
- fetching. If there is a remote-tracking branch corresponding to
- the upstream branch and the upstream branch was rebased since last
- fetched, the rebase uses that information to avoid rebasing
- non-local changes.
+--rebase[=false|true|preserve]::
+ When true, rebase the current branch on top of the upstream
+ branch after fetching. If there is a remote-tracking branch
+ corresponding to the upstream branch and the upstream branch
+ was rebased since last fetched, the rebase uses that information
+ to avoid rebasing non-local changes.
++
+When preserve, also rebase the current branch on top of the upstream
+branch, but pass `--preserve-merges` along to `git rebase` so that
+locally created merge commits will not be flattened.
++
+When false, merge the current branch into the upstream branch.
+
See `pull.rebase`, `branch.<name>.rebase` and `branch.autosetuprebase` in
linkgit:git-config[1] if you want to make `git pull` always use
[verse]
'git push' [--all | --mirror | --tags] [--follow-tags] [-n | --dry-run] [--receive-pack=<git-receive-pack>]
[--repo=<repository>] [-f | --force] [--prune] [-v | --verbose] [-u | --set-upstream]
+ [--force-with-lease[=<refname>[:<expect>]]]
[--no-verify] [<repository> [<refspec>...]]
DESCRIPTION
--follow-tags::
Push all the refs that would be pushed without this option,
and also push annotated tags in `refs/tags` that are missing
- from the remote but are pointing at committish that are
+ from the remote but are pointing at commit-ish that are
reachable from the refs being pushed.
--receive-pack=<git-receive-pack>::
repository over ssh, and you do not have the program in
a directory on the default $PATH.
+--[no-]force-with-lease::
+--force-with-lease=<refname>::
+--force-with-lease=<refname>:<expect>::
+ Usually, "git push" refuses to update a remote ref that is
+ not an ancestor of the local ref used to overwrite it.
++
+This option bypasses the check, but instead requires that the
+current value of the ref to be the expected value. "git push"
+fails otherwise.
++
+Imagine that you have to rebase what you have already published.
+You will have to bypass the "must fast-forward" rule in order to
+replace the history you originally published with the rebased history.
+If somebody else built on top of your original history while you are
+rebasing, the tip of the branch at the remote may advance with her
+commit, and blindly pushing with `--force` will lose her work.
++
+This option allows you to say that you expect the history you are
+updating is what you rebased and want to replace. If the remote ref
+still points at the commit you specified, you can be sure that no
+other people did anything to the ref (it is like taking a "lease" on
+the ref without explicitly locking it, and you update the ref while
+making sure that your earlier "lease" is still valid).
++
+`--force-with-lease` alone, without specifying the details, will protect
+all remote refs that are going to be updated by requiring their
+current value to be the same as the remote-tracking branch we have
+for them, unless specified with a `--force-with-lease=<refname>:<expect>`
+option that explicitly states what the expected value is.
++
+`--force-with-lease=<refname>`, without specifying the expected value, will
+protect the named ref (alone), if it is going to be updated, by
+requiring its current value to be the same as the remote-tracking
+branch we have for it.
++
+`--force-with-lease=<refname>:<expect>` will protect the named ref (alone),
+if it is going to be updated, by requiring its current value to be
+the same as the specified value <expect> (which is allowed to be
+different from the remote-tracking branch we have for the refname,
+or we do not even have to have such a remote-tracking branch when
+this form is used).
++
+Note that all forms other than `--force-with-lease=<refname>:<expect>`
+that specifies the expected current value of the ref explicitly are
+still experimental and their semantics may change as we gain experience
+with this feature.
++
+"--no-force-with-lease" will cancel all the previous --force-with-lease on the
+command line.
+
-f::
--force::
Usually, the command refuses to update a remote ref that is
not an ancestor of the local ref used to overwrite it.
- This flag disables the check. This can cause the
- remote repository to lose commits; use it with care.
- Note that `--force` applies to all the refs that are pushed,
- hence using it with `push.default` set to `matching` or with
- multiple push destinations configured with `remote.*.push`
- may overwrite refs other than the current branch (including
- local refs that are strictly behind their remote counterpart).
- To force a push to only one branch, use a `+` in front of the
- refspec to push (e.g `git push origin +master` to force a push
- to the `master` branch). See the `<refspec>...` section above
- for details.
+ Also, when `--force-with-lease` option is used, the command refuses
+ to update a remote ref whose current value does not match
+ what is expected.
++
+This flag disables these checks, and can cause the remote repository
+to lose commits; use it with care.
++
+Note that `--force` applies to all the refs that are pushed, hence
+using it with `push.default` set to `matching` or with multiple push
+destinations configured with `remote.*.push` may overwrite refs
+other than the current branch (including local refs that are
+strictly behind their remote counterpart). To force a push to only
+one branch, use a `+` in front of the refspec to push (e.g `git push
+origin +master` to force a push to the `master` branch). See the
+`<refspec>...` section above for details.
--repo=<repository>::
This option is only relevant if no <repository> argument is
reverting a topic branch merge, as this option recreates the topic branch with
fresh commits so it can be remerged successfully without needing to "revert
the reversion" (see the
-link:howto/revert-a-faulty-merge.txt[revert-a-faulty-merge How-To] for details).
+link:howto/revert-a-faulty-merge.html[revert-a-faulty-merge How-To] for details).
--ignore-whitespace::
--whitespace=<option>::
You may find this helpful after reverting a topic branch merge, as this option
recreates the topic branch with fresh commits so it can be remerged
successfully without needing to "revert the reversion" (see the
-link:howto/revert-a-faulty-merge.txt[revert-a-faulty-merge How-To] for details).
+link:howto/revert-a-faulty-merge.html[revert-a-faulty-merge How-To] for details).
include::merge-strategies.txt[]
changes introduced by commits that are not ancestors of the previously
reverted merge. This may or may not be what you want.
+
-See the link:howto/revert-a-faulty-merge.txt[revert-a-faulty-merge How-To] for
+See the link:howto/revert-a-faulty-merge.html[revert-a-faulty-merge How-To] for
more details.
--no-edit::
git diff --name-only --diff-filter=D -z | xargs -0 git rm --cached
----------------
-Submodules
-~~~~~~~~~~
+SUBMODULES
+----------
Only submodules using a gitfile (which means they were cloned
with a Git version 1.7.8 or newer) will be removed from the work
tree, as their repository lives inside the .git directory of the
superproject. If a submodule (or one of those nested inside it)
still uses a .git directory, `git rm` will fail - no matter if forced
-or not - to protect the submodule's history.
+or not - to protect the submodule's history. If it exists the
+submodule.<name> section in the linkgit:gitmodules[5] file will also
+be removed and that file will be staged (unless --cached or -n are used).
A submodule is considered up-to-date when the HEAD is the same as
recorded in the index, no tracked files are modified and no untracked
SYNOPSIS
--------
[verse]
-'git update-ref' [-m <reason>] (-d <ref> [<oldvalue>] | [--no-deref] <ref> <newvalue> [<oldvalue>])
+'git update-ref' [-m <reason>] (-d <ref> [<oldvalue>] | [--no-deref] <ref> <newvalue> [<oldvalue>] | --stdin [-z])
DESCRIPTION
-----------
With `-d` flag, it deletes the named <ref> after verifying it
still contains <oldvalue>.
+With `--stdin`, update-ref reads instructions from standard input and
+performs all modifications together. Specify commands of the form:
+
+ update SP <ref> SP <newvalue> [SP <oldvalue>] LF
+ create SP <ref> SP <newvalue> LF
+ delete SP <ref> [SP <oldvalue>] LF
+ verify SP <ref> [SP <oldvalue>] LF
+ option SP <opt> LF
+
+Quote fields containing whitespace as if they were strings in C source
+code. Alternatively, use `-z` to specify commands without quoting:
+
+ update SP <ref> NUL <newvalue> NUL [<oldvalue>] NUL
+ create SP <ref> NUL <newvalue> NUL
+ delete SP <ref> NUL [<oldvalue>] NUL
+ verify SP <ref> NUL [<oldvalue>] NUL
+ option SP <opt> NUL
+
+Lines of any other format or a repeated <ref> produce an error.
+Command meanings are:
+
+update::
+ Set <ref> to <newvalue> after verifying <oldvalue>, if given.
+ Specify a zero <newvalue> to ensure the ref does not exist
+ after the update and/or a zero <oldvalue> to make sure the
+ ref does not exist before the update.
+
+create::
+ Create <ref> with <newvalue> after verifying it does not
+ exist. The given <newvalue> may not be zero.
+
+delete::
+ Delete <ref> after verifying it exists with <oldvalue>, if
+ given. If given, <oldvalue> may not be zero.
+
+verify::
+ Verify <ref> against <oldvalue> but do not change it. If
+ <oldvalue> zero or missing, the ref must not exist.
+
+option::
+ Modify behavior of the next command naming a <ref>.
+ The only valid option is `no-deref` to avoid dereferencing
+ a symbolic ref.
+
+Use 40 "0" or the empty string to specify a zero value, except that
+with `-z` an empty <oldvalue> is considered missing.
+
+If all <ref>s can be locked with matching <oldvalue>s
+simultaneously, all modifications are performed. Otherwise, no
+modifications are performed. Note that while each individual
+<ref> is updated or deleted atomically, a concurrent reader may
+still see a subset of the modifications.
Logging Updates
---------------
SYNOPSIS
--------
[verse]
-'git' [--version] [--help] [-c <name>=<value>]
+'git' [--version] [--help] [-C <path>] [-c <name>=<value>]
[--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
[-p|--paginate|--no-pager] [--no-replace-objects] [--bare]
[--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
because `git --help ...` is converted internally into `git
help ...`.
+-C <path>::
+ Run as if git was started in '<path>' instead of the current working
+ directory. When multiple `-C` options are given, each subsequent
+ non-absolute `-C <path>` is interpreted relative to the preceding `-C
+ <path>`.
++
+This option affects options that expect path name like `--git-dir` and
+`--work-tree` in that their interpretations of the path names would be
+made relative to the working directory caused by the `-C` option. For
+example the following invocations are equivalent:
+
+ git --git-dir=a.git --work-tree=b -C c status
+ git --git-dir=c/a.git --work-tree=c/b status
+
-c <name>=<value>::
Pass a configuration parameter to the command. The value
given will override values from configuration files.
linkgit:git-replace[1] for more information.
--literal-pathspecs::
- Treat pathspecs literally, rather than as glob patterns. This is
- equivalent to setting the `GIT_LITERAL_PATHSPECS` environment
+ Treat pathspecs literally (i.e. no globbing, no pathspec magic).
+ This is equivalent to setting the `GIT_LITERAL_PATHSPECS` environment
variable to `1`.
+--glob-pathspecs:
+ Add "glob" magic to all pathspec. This is equivalent to setting
+ the `GIT_GLOB_PATHSPECS` environment variable to `1`. Disabling
+ globbing on individual pathspecs can be done using pathspec
+ magic ":(literal)"
+
+--noglob-pathspecs:
+ Add "literal" magic to all pathspec. This is equivalent to setting
+ the `GIT_NOGLOB_PATHSPECS` environment variable to `1`. Enabling
+ globbing on individual pathspecs can be done using pathspec
+ magic ":(glob)"
+
+--icase-pathspecs:
+ Add "icase" magic to all pathspec. This is equivalent to setting
+ the `GIT_ICASE_PATHSPECS` environment variable to `1`.
GIT COMMANDS
------------
literal paths to Git (e.g., paths previously given to you by
`git ls-tree`, `--raw` diff output, etc).
+GIT_GLOB_PATHSPECS::
+ Setting this variable to `1` will cause Git to treat all
+ pathspecs as glob patterns (aka "glob" magic).
+
+GIT_NOGLOB_PATHSPECS::
+ Setting this variable to `1` will cause Git to treat all
+ pathspecs as literal (aka "literal" magic).
+
+GIT_ICASE_PATHSPECS::
+ Setting this variable to `1` will cause Git to treat all
+ pathspecs as case-insensitive.
+
Discussion[[Discussion]]
------------------------
+
---------------------------------------------
$ git describe -h
-usage: git describe [options] <committish>*
+usage: git describe [options] <commit-ish>*
or: git describe [options] --dirty
--contains find the tag that comes after the commit
repository to a mailing list. See linkgit:githooks[5].
You can enforce finer grained permissions using update hooks. See
-link:howto/update-hook-example.txt[Controlling access to branches using
+link:howto/update-hook-example.html[Controlling access to branches using
update hooks].
Providing CVS Access to a Git Repository
When choosing between 'push' and 'export', Git prefers 'push'.
Other frontends may have some other order of preference.
+'no-private-update'::
+ When using the 'refspec' capability, git normally updates the
+ private ref on successful push. This update is disabled when
+ the remote-helper declares the capability 'no-private-update'.
+
Capabilities for Fetching
^^^^^^^^^^^^^^^^^^^^^^^^^
+
Supported commands: 'list', 'import'.
+'check-connectivity'::
+ Can guarantee that when a clone is requested, the received
+ pack is self contained and is connected.
+
If a helper advertises 'connect', Git will use it if possible and
fall back to another capability if the helper requests so when
connecting (see the 'connect' command under COMMANDS).
advertised with this capability must cover all refs reported by
the list command. If no 'refspec' capability is advertised,
there is an implied `refspec *:*`.
++
+When writing remote-helpers for decentralized version control
+systems, it is advised to keep a local copy of the repository to
+interact with, and to let the private namespace refs point to this
+local repository, while the refs/remotes namespace is used to track
+the remote repository.
'bidi-import'::
This modifies the 'import' capability.
GIT_DIR/objects/pack which is keeping a pack until refs can be
suitably updated.
+
+If option 'check-connectivity' is requested, the helper must output
+'connectivity-ok' if the clone is self-contained and connected.
++
Supported if the helper has the "fetch" capability.
'push' +<src>:<dst>::
must not rely on this option being set before
connect request occurs.
+'option check-connectivity' \{'true'|'false'\}::
+ Request the helper to check connectivity of a clone.
+
SEE ALSO
--------
linkgit:git-remote[1]
to the top <<def_directory,directory>> of the stored
revision.
+[[def_commit-ish]]commit-ish (also committish)::
+ A <<def_commit_object,commit object>> or an
+ <<def_object,object>> that can be recursively dereferenced to
+ a commit object.
+ The following are all commit-ishes:
+ a commit object,
+ a <<def_tag_object,tag object>> that points to a commit
+ object,
+ a tag object that points to a tag object that points to a
+ commit object,
+ etc.
+
[[def_core_git]]core Git::
Fundamental data structures and utilities of Git. Exposes only limited
source code management tools.
against the path.
+
The "magic signature" consists of an ASCII symbol that is not
-alphanumeric. Currently only the slash `/` is recognized as a
-"magic signature": it makes the pattern match from the root of
-the working tree, even when you are running the command from
-inside a subdirectory.
+alphanumeric.
++
+--
+top `/`;;
+ The magic word `top` (mnemonic: `/`) makes the pattern match
+ from the root of the working tree, even when you are running
+ the command from inside a subdirectory.
+
+literal;;
+ Wildcards in the pattern such as `*` or `?` are treated
+ as literal characters.
+
+icase;;
+ Case insensitive match.
+
+glob;;
+ Git treats the pattern as a shell glob suitable for
+ consumption by fnmatch(3) with the FNM_PATHNAME flag:
+ wildcards in the pattern will not match a / in the pathname.
+ For example, "Documentation/{asterisk}.html" matches
+ "Documentation/git.html" but not "Documentation/ppc/ppc.html"
+ or "tools/perf/Documentation/perf.html".
++
+Two consecutive asterisks ("`**`") in patterns matched against
+full pathname may have special meaning:
+
+ - A leading "`**`" followed by a slash means match in all
+ directories. For example, "`**/foo`" matches file or directory
+ "`foo`" anywhere, the same as pattern "`foo`". "**/foo/bar"
+ matches file or directory "`bar`" anywhere that is directly
+ under directory "`foo`".
+
+ - A trailing "/**" matches everything inside. For example,
+ "abc/**" matches all files inside directory "abc", relative
+ to the location of the `.gitignore` file, with infinite depth.
+
+ - A slash followed by two consecutive asterisks then a slash
+ matches zero or more directories. For example, "`a/**/b`"
+ matches "`a/b`", "`a/x/b`", "`a/x/y/b`" and so on.
+
+ - Other consecutive asterisks are considered invalid.
++
+Glob magic is incompatible with literal magic.
+--
++
+Currently only the slash `/` is recognized as the "magic signature",
+but it is envisioned that we will support more types of magic in later
+versions of Git.
+
A pathspec with only a colon means "there is no pathspec". This form
should not be combined with other pathspec.
to the result.
[[def_ref]]ref::
- A 40-byte hex representation of a <<def_SHA1,SHA-1>> or a name that
- denotes a particular <<def_object,object>>. They may be stored in
- a file under `$GIT_DIR/refs/` directory, or
- in the `$GIT_DIR/packed-refs` file.
+ A name that begins with `refs/` (e.g. `refs/heads/master`)
+ that points to an <<def_object_name,object name>> or another
+ ref (the latter is called a <<def_symref,symbolic ref>>).
+ For convenience, a ref can sometimes be abbreviated when used
+ as an argument to a Git command; see linkgit:gitrevisions[7]
+ for details.
+ Refs are stored in the <<def_repository,repository>>.
++
+The ref namespace is hierarchical.
+Different subhierarchies are used for different purposes (e.g. the
+`refs/heads/` hierarchy is used to represent local branches).
++
+There are a few special-purpose refs that do not begin with `refs/`.
+The most notable example is `HEAD`.
[[def_reflog]]reflog::
A reflog shows the local "history" of a ref. In other words,
with refs to the associated blob and/or tree objects. A
<<def_tree,tree>> is equivalent to a <<def_directory,directory>>.
-[[def_tree-ish]]tree-ish::
- A <<def_ref,ref>> pointing to either a <<def_commit_object,commit
- object>>, a <<def_tree_object,tree object>>, or a <<def_tag_object,tag
- object>> pointing to a tag or commit or tree object.
+[[def_tree-ish]]tree-ish (also treeish)::
+ A <<def_tree_object,tree object>> or an <<def_object,object>>
+ that can be recursively dereferenced to a tree object.
+ Dereferencing a <<def_commit_object,commit object>> yields the
+ tree object corresponding to the <<def_revision,revision>>'s
+ top <<def_directory,directory>>.
+ The following are all tree-ishes:
+ a <<def_commit-ish,commit-ish>>,
+ a tree object,
+ a <<def_tag_object,tag object>> that points to a tree object,
+ a tag object that points to a tag object that points to a tree
+ object,
+ etc.
[[def_unmerged_index]]unmerged index::
An <<def_index,index>> which contains unmerged
Packing 0 objects
Unpacking 0 objects
-* committish: e3a693c... refs/heads/master from .
+* commit-ish: e3a693c... refs/heads/master from .
Trying to merge e3a693c... into 8c1f5f0... using 10d781b...
Committed merge 7fb9b7262a1d1e0a47bbfdcbbcf50ce0635d3f8f
cache.h | 8 ++++----
+<start> and <end> can take one of these forms:
+
- number
+
If <start> or <end> is a number, it specifies an
- /regex/
+
This form will use the first line matching the given
-POSIX regex. If <end> is a regex, it will search
+POSIX regex. If <start> is a regex, it will search from the end of
+the previous `-L` range, if any, otherwise from the start of file.
+If <start> is ``^/regex/'', it will search from the start of file.
+If <end> is a regex, it will search
starting at the line given by <start>.
+
+
This is only valid for <end> and will specify a number
of lines before or after the line given by <start>.
-+
-- :regex
+
-If the option's argument is of the form :regex, it denotes the range
+If ``:<regex>'' is given in place of <start> and <end>, it denotes the range
from the first funcname line that matches <regex>, up to the next
-funcname line.
-+
+funcname line. ``:<regex>'' searches from the end of the previous `-L` range,
+if any, otherwise from the start of file.
+``^:<regex>'' searches from the start of file.
'<rev>{caret}\{<type>\}', e.g. 'v0.99.8{caret}\{commit\}'::
A suffix '{caret}' followed by an object type name enclosed in
- brace pair means the object
- could be a tag, and dereference the tag recursively until an
- object of that type is found or the object cannot be
- dereferenced anymore (in which case, barf). '<rev>{caret}0'
+ brace pair means dereference the object at '<rev>' recursively until
+ an object of type '<type>' is found or the object cannot be
+ dereferenced anymore (in which case, barf).
+ For example, if '<rev>' is a commit-ish, '<rev>{caret}\{commit\}'
+ describes the corresponding commit object.
+ Similarly, if '<rev>' is a tree-ish, '<rev>{caret}\{tree\}'
+ describes the corresponding tree object.
+ '<rev>{caret}0'
is a short-hand for '<rev>{caret}\{commit\}'.
+
'rev{caret}\{object\}' can be used to make sure 'rev' names an
object that exists, without requiring 'rev' to be a tag, and
without dereferencing 'rev'; because a tag is already an object,
it does not have to be dereferenced even once to get to an object.
++
+'rev{caret}\{tag\}' can be used to ensure that 'rev' identifies an
+existing tag object.
'<rev>{caret}\{\}', e.g. 'v0.99.8{caret}\{\}'::
A suffix '{caret}' followed by an empty brace pair
* is_inside_git_dir()
* is_inside_work_tree()
* setup_work_tree()
-* get_pathspec()
(Dscho)
+
+Pathspec
+--------
+
+See glossary-context.txt for the syntax of pathspec. In memory, a
+pathspec set is represented by "struct pathspec" and is prepared by
+parse_pathspec(). This function takes several arguments:
+
+- magic_mask specifies what features that are NOT supported by the
+ following code. If a user attempts to use such a feature,
+ parse_pathspec() can reject it early.
+
+- flags specifies other things that the caller wants parse_pathspec to
+ perform.
+
+- prefix and args come from cmd_* functions
+
+get_pathspec() is obsolete and should never be used in new code.
+
+parse_pathspec() helps catch unsupported features and reject them
+politely. At a lower level, different pathspec-related functions may
+not support the same set of features. Such pathspec-sensitive
+functions are guarded with GUARD_PATHSPEC(), which will die in an
+unfriendly way when an unsupported feature is requested.
+
+The command designers are supposed to make sure that GUARD_PATHSPEC()
+never dies. They have to make sure all unsupported features are caught
+by parse_pathspec(), not by GUARD_PATHSPEC. grepping GUARD_PATHSPEC()
+should give the designers all pathspec-sensitive codepaths and what
+features they support.
+
+A similar process is applied when a new pathspec magic is added. The
+designer lifts the GUARD_PATHSPEC restriction in the functions that
+support the new magic. At the same time (s)he has to make sure this
+new feature will be caught at parse_pathspec() in commands that cannot
+handle the new magic in some cases. grepping parse_pathspec() should
+help.
link:http://www.ietf.org/rfc/rfc1738.txt[RFC 1738: Uniform Resource Locators (URL)]
link:http://www.ietf.org/rfc/rfc2616.txt[RFC 2616: Hypertext Transfer Protocol -- HTTP/1.1]
-link:technical/pack-protocol.txt
-link:technical/protocol-capabilities.txt
+link:technical/pack-protocol.html
+link:technical/protocol-capabilities.html
<linus> Yes, we always write out most recent first
-For the other record:
-
- <pasky> njs`: http://pastebin.com/547965
-
-The 'net never forgets, so that should be good until the end of time.
-
<njs`> And, yeah, I got the part about deeper-in-history stuff
having worse IO characteristics, one sort of doesn't care.
-Git User's Manual (for version 1.5.3 or newer)
-______________________________________________
-
+Git User Manual
+_______________
Git is a fast distributed revision control system.
The best way to see how this works is using the linkgit:gitk[1]
command; running gitk now on a Git repository and looking for merge
-commits will help understand how the Git organizes history.
+commits will help understand how Git organizes history.
In the following, we say that commit X is "reachable" from commit Y
if commit X is an ancestor of commit Y. Equivalently, you could say
a summary of the commands:
`git branch`::
- list all branches
+ list all branches.
`git branch <branch>`::
create a new branch named `<branch>`, referencing the same
- point in history as the current branch
+ point in history as the current branch.
`git branch <branch> <start-point>`::
create a new branch named `<branch>`, referencing
`<start-point>`, which may be specified any way you like,
- including using a branch name or a tag name
+ including using a branch name or a tag name.
`git branch -d <branch>`::
- delete the branch `<branch>`; if the branch you are deleting
- points to a commit which is not reachable from the current
- branch, this command will fail with a warning.
+ delete the branch `<branch>`; if the branch is not fully
+ merged in its upstream branch or contained in the current branch,
+ this command will fail with a warning.
`git branch -D <branch>`::
- even if the branch points to a commit not reachable
- from the current branch, you may know that that commit
- is still reachable from some other branch or tag. In that
- case it is safe to use this command to force Git to delete
- the branch.
+ delete the branch `<branch>` irrespective of its merged status.
`git checkout <branch>`::
make the current branch `<branch>`, updating the working
- directory to reflect the version referenced by `<branch>`
+ directory to reflect the version referenced by `<branch>`.
`git checkout -b <new> <start-point>`::
create a new branch `<new>` referencing `<start-point>`, and
check it out.
------------------------------------------------
$ git checkout v2.6.17
-Note: moving to "v2.6.17" which isn't a local branch
-If you want to create a new branch from this checkout, you may do so
-(now or later) by using -b with the checkout command again. Example:
- git checkout -b <new_branch_name>
+Note: checking out 'v2.6.17'.
+
+You are in 'detached HEAD' state. You can look around, make experimental
+changes and commit them, and you can discard any commits you make in this
+state without impacting any branches by performing another checkout.
+
+If you want to create a new branch to retain commits you create, you may
+do so (now or later) by using -b with the checkout command again. Example:
+
+ git checkout -b new_branch_name
+
HEAD is now at 427abfa... Linux v2.6.17
------------------------------------------------
$ cat .git/HEAD
427abfa28afedffadfca9dd8b067eb6d36bac53f
$ git branch
-* (no branch)
+* (detached from v2.6.17)
master
------------------------------------------------
-------------------------------------------------
Or you could recall that the `...` operator selects all commits
-contained reachable from either one reference or the other but not
+reachable from either one reference or the other but not
both; so
-------------------------------------------------
$ gitk e05db0fd..
-------------------------------------------------
-Or you can use linkgit:git-name-rev[1], which will give the commit a
+or you can use linkgit:git-name-rev[1], which will give the commit a
name based on any tag it finds pointing to one of the commit's
descendants:
As yet another alternative, the linkgit:git-show-branch[1] command lists
the commits reachable from its arguments with a display on the left-hand
-side that indicates which arguments that commit is reachable from. So,
-you can run something like
+side that indicates which arguments that commit is reachable from.
+So, if you run something like
-------------------------------------------------
$ git show-branch e05db0fd v1.5.0-rc0 v1.5.0-rc1 v1.5.0-rc2
...
-------------------------------------------------
-then search for a line that looks like
+then a line like
-------------------------------------------------
+ ++ [e05db0fd] Fix warnings in sha1_file.c - use C99 printf format if
available
-------------------------------------------------
-Which shows that e05db0fd is reachable from itself, from v1.5.0-rc1, and
-from v1.5.0-rc2, but not from v1.5.0-rc0.
+shows that e05db0fd is reachable from itself, from v1.5.0-rc1,
+and from v1.5.0-rc2, and not from v1.5.0-rc0.
[[showing-commits-unique-to-a-branch]]
Showing commits unique to a given branch
Modifying the index is easy:
-To update the index with the new contents of a modified file, use
-
--------------------------------------------------
-$ git add path/to/file
--------------------------------------------------
-
-To add the contents of a new file to the index, use
+To update the index with the contents of a new or modified file, use
-------------------------------------------------
$ git add path/to/file
-------------------------------------------------
-To remove a file from the index and from the working tree,
+To remove a file from the index and from the working tree, use
-------------------------------------------------
$ git rm path/to/file
$ git merge branch
-------------------------------------------------
-are roughly equivalent. The former is actually very commonly used.
+are roughly equivalent.
[[submitting-patches]]
Submitting patches to a project
-------------------------------------------------
(See also
-link:howto/setup-git-server-over-http.txt[setup-git-server-over-http]
+link:howto/setup-git-server-over-http.html[setup-git-server-over-http]
for a slightly more sophisticated setup using WebDAV which also
allows pushing over HTTP.)
$ ... patch ... test ... commit [ ... patch ... test ... commit ]*
-------------------------------------------------
-When you are happy with the state of this change, you can pull it into the
+When you are happy with the state of this change, you can merge it into the
"test" branch in preparation to make it public:
-------------------------------------------------
-$ git checkout test && git pull . speed-up-spinlocks
+$ git checkout test && git merge speed-up-spinlocks
-------------------------------------------------
It is unlikely that you would have any conflicts here ... but you might if you
means that the patches can be moved into the `release` tree in any order.
-------------------------------------------------
-$ git checkout release && git pull . speed-up-spinlocks
+$ git checkout release && git merge speed-up-spinlocks
-------------------------------------------------
After a while, you will have a number of branches, and despite the
You can save space and make Git faster by moving these loose objects in
to a "pack file", which stores a group of objects in an efficient
compressed format; the details of how pack files are formatted can be
-found in link:technical/pack-format.txt[technical/pack-format.txt].
+found in link:technical/pack-format.html[pack format].
To put the loose objects into a pack, just run git repack:
------------------------------------------------
$ git repack
-Generating pack...
-Done counting 6020 objects.
-Deltifying 6020 objects.
- 100% (6020/6020) done
-Writing 6020 objects.
- 100% (6020/6020) done
-Total 6020, written 6020 (delta 4070), reused 0 (delta 0)
-Pack pack-3e54ad29d5b2e05838c75df582c65257b8d08e1c created.
+Counting objects: 6020, done.
+Delta compression using up to 4 threads.
+Compressing objects: 100% (6020/6020), done.
+Writing objects: 100% (6020/6020), done.
+Total 6020 (delta 4070), reused 0 (delta 0)
------------------------------------------------
-You can then run
+This creates a single "pack file" in .git/objects/pack/
+containing all currently unpacked objects. You can then run
------------------------------------------------
$ git prune
$ git prune
------------------------------------------------
-and they'll be gone. But you should only run `git prune` on a quiescent
+and they'll be gone. (You should only run `git prune` on a quiescent
repository--it's kind of like doing a filesystem fsck recovery: you
don't want to do that while the filesystem is mounted.
-
-(The same is true of `git fsck` itself, btw, but since
-`git fsck` never actually *changes* the repository, it just reports
-on what it found, `git fsck` itself is never 'dangerous' to run.
-Running it while somebody is actually changing the repository can cause
-confusing and scary messages, but it won't actually do anything bad. In
-contrast, running `git prune` while somebody is actively changing the
-repository is a *BAD* idea).
+`git prune` is designed not to cause any harm in such cases of concurrent
+accesses to a repository but you might receive confusing or scary messages.)
[[recovering-from-repository-corruption]]
Recovering from repository corruption
manually check them out; earlier versions won't recognize the submodules at
all.
-To see how submodule support works, create (for example) four example
+To see how submodule support works, create four example
repositories that can be used later as a submodule:
-------------------------------------------------
-------------------------------------------------
$ git branch
-* (no branch)
+* (detached from d266b98)
master
-------------------------------------------------
previous states represented by other commits.
In other words, while a "tree" represents a particular directory state
-of a working directory, a "commit" represents that state in "time",
+of a working directory, a "commit" represents that state in time,
and explains how we got there.
You create a commit object by giving it the tree that describes the
result to the file pointed at by `.git/HEAD`, so that we can always see
what the last committed state was.
-Here is an ASCII art by Jon Loeliger that illustrates how
-various pieces fit together.
+Here is a picture that illustrates how various pieces fit together:
------------
Merging multiple trees
----------------------
-Git helps you do a three-way merge, which you can expand to n-way by
-repeating the merge procedure arbitrary times until you finally
-"commit" the state. The normal situation is that you'd only do one
-three-way merge (two parents), and commit it, but if you like to, you
-can do multiple parents in one go.
+Git can help you perform a three-way merge, which can in turn be
+used for a many-way merge by repeating the merge procedure several
+times. The usual situation is that you only do one three-way merge
+(reconciling two lines of history) and commit the result, but if
+you like to, you can merge several branches in one go.
-To do a three-way merge, you need the two sets of "commit" objects
-that you want to merge, use those to find the closest common parent (a
-third "commit" object), and then use those commit objects to find the
-state of the directory ("tree" object) at these points.
+To perform a three-way merge, you start with the two commits you
+want to merge, find their closest common parent (a third commit),
+and compare the trees corresponding to these three commits.
-To get the "base" for the merge, you first look up the common parent
-of two commits with
+To get the "base" for the merge, look up the common parent of two
+commits:
-------------------------------------------------
$ git merge-base <commit1> <commit2>
-------------------------------------------------
-which will return you the commit they are both based on. You should
-now look up the "tree" objects of those commits, which you can easily
-do with (for example)
+This prints the name of a commit they are both based on. You should
+now look up the tree objects of those commits, which you can easily
+do with
-------------------------------------------------
$ git cat-file commit <commitname> | head -1
that is used to name the object is the hash of the original data
plus this header, so `sha1sum` 'file' does not match the object name
for 'file'.
-(Historical note: in the dawn of the age of Git the hash
-was the SHA-1 of the 'compressed' object.)
As a result, the general consistency of an object can always be tested
independently of the contents or the type of the object: all objects can
# Define NO_MSGFMT_EXTENDED_OPTIONS if your implementation of msgfmt
# doesn't support GNU extensions like --check and --statistics
#
-# Define NEEDS_CLIPPED_WRITE if your write(2) cannot write more than
-# INT_MAX bytes at once (e.g. MacOS X).
-#
# Define HAVE_PATHS_H if you have paths.h and want to use the default PATH
# it specifies.
#
SCRIPT_PERL += git-send-email.perl
SCRIPT_PERL += git-svn.perl
-SCRIPT_PYTHON += git-remote-testpy.py
SCRIPT_PYTHON += git-p4.py
NO_INSTALL += git-remote-testgit
-NO_INSTALL += git-remote-testpy
# Generated files for scripts
SCRIPT_SH_GEN = $(patsubst %.sh,%,$(SCRIPT_SH))
TEST_PROGRAMS_NEED_X += test-string-list
TEST_PROGRAMS_NEED_X += test-subprocess
TEST_PROGRAMS_NEED_X += test-svn-fe
+TEST_PROGRAMS_NEED_X += test-urlmatch-normalization
TEST_PROGRAMS_NEED_X += test-wildmatch
TEST_PROGRAMS = $(patsubst %,%$X,$(TEST_PROGRAMS_NEED_X))
LIB_H += tree.h
LIB_H += unpack-trees.h
LIB_H += url.h
+LIB_H += urlmatch.h
LIB_H += userdiff.h
LIB_H += utf8.h
LIB_H += varint.h
LIB_OBJS += tree-walk.o
LIB_OBJS += unpack-trees.o
LIB_OBJS += url.o
+LIB_OBJS += urlmatch.o
LIB_OBJS += usage.o
LIB_OBJS += userdiff.o
LIB_OBJS += utf8.o
MSGFMT += --check --statistics
endif
-ifdef NEEDS_CLIPPED_WRITE
- BASIC_CFLAGS += -DNEEDS_CLIPPED_WRITE
- COMPAT_OBJS += compat/clipped-write.o
-endif
-
ifneq (,$(XDL_FAST_HASH))
BASIC_CFLAGS += -DXDL_FAST_HASH
endif
endif
ifndef NO_PERL
$(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' localedir='$(localedir_SQ)' all
-endif
-ifndef NO_PYTHON
- $(QUIET_SUBDIR0)git_remote_helpers $(QUIET_SUBDIR1) PYTHON_PATH='$(PYTHON_PATH_SQ)' prefix='$(prefix_SQ)' all
endif
$(QUIET_SUBDIR0)templates $(QUIET_SUBDIR1) SHELL_PATH='$(SHELL_PATH_SQ)' PERL_PATH='$(PERL_PATH_SQ)'
$(SCRIPT_PYTHON_GEN): GIT-CFLAGS GIT-PREFIX GIT-PYTHON-VARS
$(SCRIPT_PYTHON_GEN): % : %.py
$(QUIET_GEN)$(RM) $@ $@+ && \
- INSTLIBDIR=`MAKEFLAGS= $(MAKE) -C git_remote_helpers -s \
- --no-print-directory prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' \
- instlibdir` && \
sed -e '1s|#!.*python|#!$(PYTHON_PATH_SQ)|' \
- -e 's|\(os\.getenv("GITPYTHONLIB"\)[^)]*)|\1,"@@INSTLIBDIR@@")|' \
- -e 's|@@INSTLIBDIR@@|'"$$INSTLIBDIR"'|g' \
$< >$@+ && \
chmod +x $@+ && \
mv $@+ $@
$(MAKE) -C perl prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install
$(MAKE) -C gitweb install
endif
-ifndef NO_PYTHON
- $(MAKE) -C git_remote_helpers prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install
-endif
ifndef NO_TCLTK
$(MAKE) -C gitk-git install
$(MAKE) -C git-gui gitexecdir='$(gitexec_instdir_SQ)' install
ifndef NO_PERL
$(MAKE) -C gitweb clean
$(MAKE) -C perl clean
-endif
-ifndef NO_PYTHON
- $(MAKE) -C git_remote_helpers clean
endif
$(MAKE) -C templates/ clean
$(MAKE) -C t/ clean
struct archiver_context context;
struct unpack_trees_options opts;
struct tree_desc t;
- struct pathspec pathspec;
int err;
if (args->baselen > 0 && args->base[args->baselen - 1] == '/') {
git_attr_set_direction(GIT_ATTR_INDEX, &the_index);
}
- init_pathspec(&pathspec, args->pathspec);
- err = read_tree_recursive(args->tree, "", 0, 0, &pathspec,
+ err = read_tree_recursive(args->tree, "", 0, 0, &args->pathspec,
write_archive_entry, &context);
- free_pathspec(&pathspec);
if (err == READ_TREE_RECURSIVE)
err = 0;
return err;
struct pathspec pathspec;
int ret;
- init_pathspec(&pathspec, paths);
+ parse_pathspec(&pathspec, 0, 0, "", paths);
ret = read_tree_recursive(tree, "", 0, 0, &pathspec, reject_entry, NULL);
free_pathspec(&pathspec);
return ret != 0;
static void parse_pathspec_arg(const char **pathspec,
struct archiver_args *ar_args)
{
- ar_args->pathspec = pathspec = get_pathspec("", pathspec);
+ /*
+ * must be consistent with parse_pathspec in path_exists()
+ * Also if pathspec patterns are dependent, we're in big
+ * trouble as we test each one separately
+ */
+ parse_pathspec(&ar_args->pathspec, 0,
+ PATHSPEC_PREFER_FULL,
+ "", pathspec);
if (pathspec) {
while (*pathspec) {
if (**pathspec && !path_exists(ar_args->tree, *pathspec))
- die("path not found: %s", *pathspec);
+ die(_("pathspec '%s' did not match any files"), *pathspec);
pathspec++;
}
}
#ifndef ARCHIVE_H
#define ARCHIVE_H
+#include "pathspec.h"
+
struct archiver_args {
const char *base;
size_t baselen;
const unsigned char *commit_sha1;
const struct commit *commit;
time_t time;
- const char **pathspec;
+ struct pathspec pathspec;
unsigned int verbose : 1;
unsigned int worktree_attributes : 1;
unsigned int convert : 1;
if (prepare_revision_walk(revs))
die("revision walk setup failed");
if (revs->tree_objects)
- mark_edges_uninteresting(revs->commits, revs, NULL);
+ mark_edges_uninteresting(revs, NULL);
}
static void exit_if_skipped_commits(struct commit_list *tried,
struct refspec query;
memset(&query, 0, sizeof(struct refspec));
query.dst = tracking_branch;
- return !(remote_find_tracking(remote, &query) ||
- prefixcmp(query.src, "refs/heads/"));
+ return !remote_find_tracking(remote, &query);
}
static int validate_remote_tracking_branch(char *ref)
hashcpy(sha1, commit->object.sha1);
if (!dont_change_ref) {
- lock = lock_any_ref_for_update(ref.buf, NULL, 0);
+ lock = lock_any_ref_for_update(ref.buf, NULL, 0, NULL);
if (!lock)
die_errno(_("Failed to lock ref for update"));
}
start_name);
if (real_ref && track)
- setup_tracking(ref.buf+11, real_ref, track, quiet);
+ setup_tracking(ref.buf + 11, real_ref, track, quiet);
if (!dont_change_ref)
if (write_ref_sha1(lock, sha1, msg) < 0)
}
}
-static void update_files_in_cache(const char *prefix, const char **pathspec,
+static void update_files_in_cache(const char *prefix,
+ const struct pathspec *pathspec,
struct update_callback_data *data)
{
struct rev_info rev;
init_revisions(&rev, prefix);
setup_revisions(0, NULL, &rev, NULL);
- init_pathspec(&rev.prune_data, pathspec);
+ if (pathspec)
+ copy_pathspec(&rev.prune_data, pathspec);
rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
rev.diffopt.format_callback = update_callback;
rev.diffopt.format_callback_data = data;
run_diff_files(&rev, DIFF_RACY_IS_MODIFIED);
}
-int add_files_to_cache(const char *prefix, const char **pathspec, int flags)
+int add_files_to_cache(const char *prefix,
+ const struct pathspec *pathspec, int flags)
{
struct update_callback_data data;
}
#define WARN_IMPLICIT_DOT (1u << 0)
-static char *prune_directory(struct dir_struct *dir, const char **pathspec,
+static char *prune_directory(struct dir_struct *dir, struct pathspec *pathspec,
int prefix, unsigned flag)
{
char *seen;
- int i, specs;
+ int i;
struct dir_entry **src, **dst;
- for (specs = 0; pathspec[specs]; specs++)
- /* nothing */;
- seen = xcalloc(specs, 1);
+ seen = xcalloc(pathspec->nr, 1);
src = dst = dir->entries;
i = dir->nr;
while (--i >= 0) {
struct dir_entry *entry = *src++;
- if (match_pathspec(pathspec, entry->name, entry->len,
- prefix, seen))
+ if (match_pathspec_depth(pathspec, entry->name, entry->len,
+ prefix, seen))
*dst++ = entry;
else if (flag & WARN_IMPLICIT_DOT)
/*
warn_pathless_add();
}
dir->nr = dst - dir->entries;
- add_pathspec_matches_against_index(pathspec, seen, specs);
+ add_pathspec_matches_against_index(pathspec, seen);
return seen;
}
-/*
- * Checks the index to see whether any path in pathspec refers to
- * something inside a submodule. If so, dies with an error message.
- */
-static void treat_gitlinks(const char **pathspec)
-{
- int i;
-
- if (!pathspec || !*pathspec)
- return;
-
- for (i = 0; pathspec[i]; i++)
- pathspec[i] = check_path_for_gitlink(pathspec[i]);
-}
-
-static void refresh(int verbose, const char **pathspec)
+static void refresh(int verbose, const struct pathspec *pathspec)
{
char *seen;
- int i, specs;
+ int i;
- for (specs = 0; pathspec[specs]; specs++)
- /* nothing */;
- seen = xcalloc(specs, 1);
+ seen = xcalloc(pathspec->nr, 1);
refresh_index(&the_index, verbose ? REFRESH_IN_PORCELAIN : REFRESH_QUIET,
pathspec, seen, _("Unstaged changes after refreshing the index:"));
- for (i = 0; i < specs; i++) {
+ for (i = 0; i < pathspec->nr; i++) {
if (!seen[i])
- die(_("pathspec '%s' did not match any files"), pathspec[i]);
+ die(_("pathspec '%s' did not match any files"),
+ pathspec->items[i].match);
}
free(seen);
}
-/*
- * Normalizes argv relative to prefix, via get_pathspec(), and then
- * runs die_if_path_beyond_symlink() on each path in the normalized
- * list.
- */
-static const char **validate_pathspec(const char **argv, const char *prefix)
-{
- const char **pathspec = get_pathspec(prefix, argv);
-
- if (pathspec) {
- const char **p;
- for (p = pathspec; *p; p++) {
- die_if_path_beyond_symlink(*p, prefix);
- }
- }
-
- return pathspec;
-}
-
int run_add_interactive(const char *revision, const char *patch_mode,
- const char **pathspec)
+ const struct pathspec *pathspec)
{
- int status, ac, pc = 0;
+ int status, ac, i;
const char **args;
- if (pathspec)
- while (pathspec[pc])
- pc++;
-
- args = xcalloc(sizeof(const char *), (pc + 5));
+ args = xcalloc(sizeof(const char *), (pathspec->nr + 6));
ac = 0;
args[ac++] = "add--interactive";
if (patch_mode)
if (revision)
args[ac++] = revision;
args[ac++] = "--";
- if (pc) {
- memcpy(&(args[ac]), pathspec, sizeof(const char *) * pc);
- ac += pc;
- }
- args[ac] = NULL;
+ for (i = 0; i < pathspec->nr; i++)
+ /* pass original pathspec, to be re-parsed */
+ args[ac++] = pathspec->items[i].original;
status = run_command_v_opt(args, RUN_GIT_CMD);
free(args);
int interactive_add(int argc, const char **argv, const char *prefix, int patch)
{
- const char **pathspec = NULL;
+ struct pathspec pathspec;
- if (argc) {
- pathspec = validate_pathspec(argv, prefix);
- if (!pathspec)
- return -1;
- }
+ parse_pathspec(&pathspec, 0,
+ PATHSPEC_PREFER_FULL |
+ PATHSPEC_SYMLINK_LEADING_PATH |
+ PATHSPEC_PREFIX_ORIGIN,
+ prefix, argv);
return run_add_interactive(NULL,
patch ? "--patch" : NULL,
- pathspec);
+ &pathspec);
}
static int edit_patch(int argc, const char **argv, const char *prefix)
git_config(git_diff_basic_config, NULL); /* no "diff" UI options */
if (read_cache() < 0)
- die (_("Could not read the index"));
+ die(_("Could not read the index"));
init_revisions(&rev, prefix);
rev.diffopt.context = 7;
DIFF_OPT_SET(&rev.diffopt, IGNORE_DIRTY_SUBMODULES);
out = open(file, O_CREAT | O_WRONLY, 0666);
if (out < 0)
- die (_("Could not open '%s' for writing."), file);
+ die(_("Could not open '%s' for writing."), file);
rev.diffopt.file = xfdopen(out, "w");
rev.diffopt.close_file = 1;
if (run_diff_files(&rev, 0))
- die (_("Could not write patch"));
+ die(_("Could not write patch"));
launch_editor(file, NULL, NULL);
child.git_cmd = 1;
child.argv = apply_argv;
if (run_command(&child))
- die (_("Could not apply '%s'"), file);
+ die(_("Could not apply '%s'"), file);
unlink(file);
free(file);
{
int exit_status = 0;
int newfd;
- const char **pathspec;
+ struct pathspec pathspec;
struct dir_struct dir;
int flags;
int add_new_files;
fprintf(stderr, _("Maybe you wanted to say 'git add .'?\n"));
return 0;
}
- pathspec = validate_pathspec(argv, prefix);
if (read_cache() < 0)
die(_("index file corrupt"));
- treat_gitlinks(pathspec);
+
+ /*
+ * Check the "pathspec '%s' did not match any files" block
+ * below before enabling new magic.
+ */
+ parse_pathspec(&pathspec, 0,
+ PATHSPEC_PREFER_FULL |
+ PATHSPEC_SYMLINK_LEADING_PATH |
+ PATHSPEC_STRIP_SUBMODULE_SLASH_EXPENSIVE,
+ prefix, argv);
if (add_new_files) {
int baselen;
+ struct pathspec empty_pathspec;
/* Set up the default git porcelain excludes */
memset(&dir, 0, sizeof(dir));
setup_standard_excludes(&dir);
}
+ memset(&empty_pathspec, 0, sizeof(empty_pathspec));
/* This picks up the paths that are not tracked */
- baselen = fill_directory(&dir, implicit_dot ? NULL : pathspec);
- if (pathspec)
- seen = prune_directory(&dir, pathspec, baselen,
+ baselen = fill_directory(&dir, implicit_dot ? &empty_pathspec : &pathspec);
+ if (pathspec.nr)
+ seen = prune_directory(&dir, &pathspec, baselen,
implicit_dot ? WARN_IMPLICIT_DOT : 0);
}
if (refresh_only) {
- refresh(verbose, pathspec);
+ refresh(verbose, &pathspec);
goto finish;
}
if (implicit_dot && prefix)
refresh_cache(REFRESH_QUIET);
- if (pathspec) {
+ if (pathspec.nr) {
int i;
if (!seen)
- seen = find_pathspecs_matching_against_index(pathspec);
- for (i = 0; pathspec[i]; i++) {
- if (!seen[i] && pathspec[i][0]
- && !file_exists(pathspec[i])) {
+ seen = find_pathspecs_matching_against_index(&pathspec);
+
+ /*
+ * file_exists() assumes exact match
+ */
+ GUARD_PATHSPEC(&pathspec,
+ PATHSPEC_FROMTOP |
+ PATHSPEC_LITERAL |
+ PATHSPEC_GLOB |
+ PATHSPEC_ICASE);
+
+ for (i = 0; i < pathspec.nr; i++) {
+ const char *path = pathspec.items[i].match;
+ if (!seen[i] &&
+ ((pathspec.items[i].magic &
+ (PATHSPEC_GLOB | PATHSPEC_ICASE)) ||
+ !file_exists(path))) {
if (ignore_missing) {
int dtype = DT_UNKNOWN;
- if (is_excluded(&dir, pathspec[i], &dtype))
- dir_add_ignored(&dir, pathspec[i], strlen(pathspec[i]));
+ if (is_excluded(&dir, path, &dtype))
+ dir_add_ignored(&dir, path, pathspec.items[i].len);
} else
die(_("pathspec '%s' did not match any files"),
- pathspec[i]);
+ pathspec.items[i].original);
}
}
free(seen);
*/
update_data.implicit_dot = prefix;
update_data.implicit_dot_len = strlen(prefix);
- pathspec = NULL;
+ free_pathspec(&pathspec);
+ memset(&pathspec, 0, sizeof(pathspec));
}
update_data.flags = flags & ~ADD_CACHE_IMPLICIT_DOT;
- update_files_in_cache(prefix, pathspec, &update_data);
+ update_files_in_cache(prefix, &pathspec, &update_data);
exit_status |= !!update_data.add_errors;
if (add_new_files)
unplug_bulk_checkin();
- finish:
+finish:
if (active_cache_changed) {
if (write_cache(newfd, active_cache, active_nr) ||
commit_locked_index(&lock_file))
#include "utf8.h"
#include "userdiff.h"
#include "line-range.h"
+#include "line-log.h"
static char blame_usage[] = N_("git blame [options] [rev-opts] [rev] [--] file");
paths[0] = origin->path;
paths[1] = NULL;
- diff_tree_setup_paths(paths, &diff_opts);
+ parse_pathspec(&diff_opts.pathspec, PATHSPEC_ALL_MAGIC, 0, "", paths);
diff_setup_done(&diff_opts);
if (is_null_sha1(origin->commit->object.sha1))
}
}
diff_flush(&diff_opts);
- diff_tree_release_paths(&diff_opts);
+ free_pathspec(&diff_opts.pathspec);
if (porigin) {
/*
* Create a freestanding copy that is not part of
struct origin *porigin = NULL;
struct diff_options diff_opts;
int i;
- const char *paths[2];
diff_setup(&diff_opts);
DIFF_OPT_SET(&diff_opts, RECURSIVE);
diff_opts.detect_rename = DIFF_DETECT_RENAME;
diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
diff_opts.single_follow = origin->path;
- paths[0] = NULL;
- diff_tree_setup_paths(paths, &diff_opts);
diff_setup_done(&diff_opts);
if (is_null_sha1(origin->commit->object.sha1))
}
}
diff_flush(&diff_opts);
- diff_tree_release_paths(&diff_opts);
+ free_pathspec(&diff_opts.pathspec);
return porigin;
}
int opt)
{
struct diff_options diff_opts;
- const char *paths[1];
int i, j;
int retval;
struct blame_list *blame_list;
DIFF_OPT_SET(&diff_opts, RECURSIVE);
diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
- paths[0] = NULL;
- diff_tree_setup_paths(paths, &diff_opts);
diff_setup_done(&diff_opts);
/* Try "find copies harder" on new path if requested;
}
reset_scanned_flag(sb);
diff_flush(&diff_opts);
- diff_tree_release_paths(&diff_opts);
+ free_pathspec(&diff_opts.pathspec);
return retval;
}
return prefix_path(prefix, prefix ? strlen(prefix) : 0, path);
}
-/*
- * Parsing of -L option
- */
-static void prepare_blame_range(struct scoreboard *sb,
- const char *bottomtop,
- long lno,
- long *bottom, long *top)
-{
- if (parse_range_arg(bottomtop, nth_line_cb, sb, lno, bottom, top, sb->path))
- usage(blame_usage);
-}
-
static int git_blame_config(const char *var, const char *value, void *cb)
{
if (!strcmp(var, "blame.showroot")) {
return 0;
}
-static int blame_bottomtop_callback(const struct option *option, const char *arg, int unset)
-{
- const char **bottomtop = option->value;
- if (!arg)
- return -1;
- if (*bottomtop)
- die("More than one '-L n,m' option given");
- *bottomtop = arg;
- return 0;
-}
-
int cmd_blame(int argc, const char **argv, const char *prefix)
{
struct rev_info revs;
const char *path;
struct scoreboard sb;
struct origin *o;
- struct blame_entry *ent;
- long dashdash_pos, bottom, top, lno;
+ struct blame_entry *ent = NULL;
+ long dashdash_pos, lno;
const char *final_commit_name = NULL;
enum object_type type;
- static const char *bottomtop = NULL;
+ static struct string_list range_list;
static int output_option = 0, opt = 0;
static int show_stats = 0;
static const char *revs_file = NULL;
OPT_STRING(0, "contents", &contents_from, N_("file"), N_("Use <file>'s contents as the final image")),
{ OPTION_CALLBACK, 'C', NULL, &opt, N_("score"), N_("Find line copies within and across files"), PARSE_OPT_OPTARG, blame_copy_callback },
{ OPTION_CALLBACK, 'M', NULL, &opt, N_("score"), N_("Find line movements within and across files"), PARSE_OPT_OPTARG, blame_move_callback },
- OPT_CALLBACK('L', NULL, &bottomtop, N_("n,m"), N_("Process only line range n,m, counting from 1"), blame_bottomtop_callback),
+ OPT_STRING_LIST('L', NULL, &range_list, N_("n,m"), N_("Process only line range n,m, counting from 1")),
OPT__ABBREV(&abbrev),
OPT_END()
};
struct parse_opt_ctx_t ctx;
int cmd_is_annotate = !strcmp(argv[0], "annotate");
+ struct range_set ranges;
+ unsigned int range_i;
+ long anchor;
git_config(git_blame_config, NULL);
init_revisions(&revs, NULL);
num_read_blob++;
lno = prepare_lines(&sb);
- bottom = top = 0;
- if (bottomtop)
- prepare_blame_range(&sb, bottomtop, lno, &bottom, &top);
- if (bottom < 1)
- bottom = 1;
- if (top < 1)
- top = lno;
- bottom--;
- if (lno < top || lno < bottom)
- die("file %s has only %lu lines", path, lno);
-
- ent = xcalloc(1, sizeof(*ent));
- ent->lno = bottom;
- ent->num_lines = top - bottom;
- ent->suspect = o;
- ent->s_lno = bottom;
+ if (lno && !range_list.nr)
+ string_list_append(&range_list, xstrdup("1"));
+
+ anchor = 1;
+ range_set_init(&ranges, range_list.nr);
+ for (range_i = 0; range_i < range_list.nr; ++range_i) {
+ long bottom, top;
+ if (parse_range_arg(range_list.items[range_i].string,
+ nth_line_cb, &sb, lno, anchor,
+ &bottom, &top, sb.path))
+ usage(blame_usage);
+ if (lno < top || ((lno || bottom) && lno < bottom))
+ die("file %s has only %lu lines", path, lno);
+ if (bottom < 1)
+ bottom = 1;
+ if (top < 1)
+ top = lno;
+ bottom--;
+ range_set_append_unsafe(&ranges, bottom, top);
+ anchor = top + 1;
+ }
+ sort_and_merge_range_set(&ranges);
+
+ for (range_i = ranges.nr; range_i > 0; --range_i) {
+ const struct range *r = &ranges.ranges[range_i - 1];
+ long bottom = r->start;
+ long top = r->end;
+ struct blame_entry *next = ent;
+ ent = xcalloc(1, sizeof(*ent));
+ ent->lno = bottom;
+ ent->num_lines = top - bottom;
+ ent->suspect = o;
+ ent->s_lno = bottom;
+ ent->next = next;
+ if (next)
+ next->prev = ent;
+ origin_incref(o);
+ }
+ origin_decref(o);
+
+ range_set_release(&ranges);
+ string_list_clear(&range_list, 0);
sb.ent = ent;
sb.path = path;
char *ref = NULL;
struct branch *branch = branch_get(branch_name);
struct strbuf fancy = STRBUF_INIT;
+ int upstream_is_gone = 0;
- if (!stat_tracking_info(branch, &ours, &theirs)) {
- if (branch && branch->merge && branch->merge[0]->dst &&
- show_upstream_ref) {
- ref = shorten_unambiguous_ref(branch->merge[0]->dst, 0);
- if (want_color(branch_use_color))
- strbuf_addf(stat, "[%s%s%s] ",
- branch_get_color(BRANCH_COLOR_UPSTREAM),
- ref, branch_get_color(BRANCH_COLOR_RESET));
- else
- strbuf_addf(stat, "[%s] ", ref);
- }
+ switch (stat_tracking_info(branch, &ours, &theirs)) {
+ case 0:
+ /* no base */
return;
+ case -1:
+ /* with "gone" base */
+ upstream_is_gone = 1;
+ break;
+ default:
+ /* with base */
+ break;
}
if (show_upstream_ref) {
strbuf_addstr(&fancy, ref);
}
- if (!ours) {
- if (ref)
+ if (upstream_is_gone) {
+ if (show_upstream_ref)
+ strbuf_addf(stat, _("[%s: gone]"), fancy.buf);
+ } else if (!ours && !theirs) {
+ if (show_upstream_ref)
+ strbuf_addf(stat, _("[%s]"), fancy.buf);
+ } else if (!ours) {
+ if (show_upstream_ref)
strbuf_addf(stat, _("[%s: behind %d]"), fancy.buf, theirs);
else
strbuf_addf(stat, _("[behind %d]"), theirs);
} else if (!theirs) {
- if (ref)
+ if (show_upstream_ref)
strbuf_addf(stat, _("[%s: ahead %d]"), fancy.buf, ours);
else
strbuf_addf(stat, _("[ahead %d]"), ours);
} else {
- if (ref)
+ if (show_upstream_ref)
strbuf_addf(stat, _("[%s: ahead %d, behind %d]"),
fancy.buf, ours, theirs);
else
enum object_type type;
unsigned long size;
unsigned long disk_size;
+ const char *rest;
/*
* If mark_query is true, we do not expand anything, but rather
*/
int mark_query;
+ /*
+ * Whether to split the input on whitespace before feeding it to
+ * get_sha1; this is decided during the mark_query phase based on
+ * whether we have a %(rest) token in our format.
+ */
+ int split_on_whitespace;
+
/*
* After a mark_query run, this object_info is set up to be
* passed to sha1_object_info_extended. It will point to the data
data->info.disk_sizep = &data->disk_size;
else
strbuf_addf(sb, "%lu", data->disk_size);
+ } else if (is_atom("rest", atom, len)) {
+ if (data->mark_query)
+ data->split_on_whitespace = 1;
+ else if (data->rest)
+ strbuf_addstr(sb, data->rest);
} else
die("unknown format element: %.*s", len, atom);
}
warn_on_object_refname_ambiguity = 0;
while (strbuf_getline(&buf, stdin, '\n') != EOF) {
- int error = batch_one_object(buf.buf, opt, &data);
+ int error;
+
+ if (data.split_on_whitespace) {
+ /*
+ * Split at first whitespace, tying off the beginning
+ * of the string and saving the remainder (or NULL) in
+ * data.rest.
+ */
+ char *p = strpbrk(buf.buf, " \t");
+ if (p) {
+ while (*p && strchr(" \t", *p))
+ *p++ = '\0';
+ }
+ data.rest = p;
+ }
+
+ error = batch_one_object(buf.buf, opt, &data);
if (error)
return error;
}
}
static int check_ignore(struct dir_struct *dir,
- const char *prefix, const char **pathspec)
+ const char *prefix, int argc, const char **argv)
{
- const char *path, *full_path;
+ const char *full_path;
char *seen;
int num_ignored = 0, dtype = DT_UNKNOWN, i;
struct exclude *exclude;
+ struct pathspec pathspec;
- if (!pathspec || !*pathspec) {
+ if (!argc) {
if (!quiet)
fprintf(stderr, "no pathspec given.\n");
return 0;
}
+ /*
+ * check-ignore just needs paths. Magic beyond :/ is really
+ * irrelevant.
+ */
+ parse_pathspec(&pathspec,
+ PATHSPEC_ALL_MAGIC & ~PATHSPEC_FROMTOP,
+ PATHSPEC_SYMLINK_LEADING_PATH |
+ PATHSPEC_STRIP_SUBMODULE_SLASH_EXPENSIVE |
+ PATHSPEC_KEEP_ORDER,
+ prefix, argv);
+
/*
* look for pathspecs matching entries in the index, since these
* should not be ignored, in order to be consistent with
* 'git status', 'git add' etc.
*/
- seen = find_pathspecs_matching_against_index(pathspec);
- for (i = 0; pathspec[i]; i++) {
- path = pathspec[i];
- full_path = prefix_path(prefix, prefix
- ? strlen(prefix) : 0, path);
- full_path = check_path_for_gitlink(full_path);
- die_if_path_beyond_symlink(full_path, prefix);
+ seen = find_pathspecs_matching_against_index(&pathspec);
+ for (i = 0; i < pathspec.nr; i++) {
+ full_path = pathspec.items[i].match;
exclude = NULL;
if (!seen[i]) {
exclude = last_exclude_matching(dir, full_path, &dtype);
}
if (!quiet && (exclude || show_non_matching))
- output_exclude(path, exclude);
+ output_exclude(pathspec.items[i].original, exclude);
if (exclude)
num_ignored++;
}
strbuf_swap(&buf, &nbuf);
}
pathspec[0] = buf.buf;
- num_ignored += check_ignore(dir, prefix, (const char **)pathspec);
+ num_ignored += check_ignore(dir, prefix,
+ 1, (const char **)pathspec);
maybe_flush_or_die(stdout, "check-ignore to stdout");
}
strbuf_release(&buf);
if (stdin_paths) {
num_ignored = check_ignore_stdin_paths(&dir, prefix);
} else {
- num_ignored = check_ignore(&dir, prefix, argv);
+ num_ignored = check_ignore(&dir, prefix, argc, argv);
maybe_flush_or_die(stdout, "ignore to stdout");
}
int branch_exists;
const char *prefix;
- const char **pathspec;
+ struct pathspec pathspec;
struct tree *source_tree;
};
return 0;
}
-static int read_tree_some(struct tree *tree, const char **pathspec)
+static int read_tree_some(struct tree *tree, const struct pathspec *pathspec)
{
- struct pathspec ps;
- init_pathspec(&ps, pathspec);
- read_tree_recursive(tree, "", 0, 0, &ps, update_some, NULL);
- free_pathspec(&ps);
+ read_tree_recursive(tree, "", 0, 0, pathspec, update_some, NULL);
/* update the index with the given tree's info
* for all args, expanding wildcards, and exit
if (opts->patch_mode)
return run_add_interactive(revision, "--patch=checkout",
- opts->pathspec);
+ &opts->pathspec);
lock_file = xcalloc(1, sizeof(struct lock_file));
newfd = hold_locked_index(lock_file, 1);
- if (read_cache_preload(opts->pathspec) < 0)
+ if (read_cache_preload(&opts->pathspec) < 0)
return error(_("corrupt index file"));
if (opts->source_tree)
- read_tree_some(opts->source_tree, opts->pathspec);
+ read_tree_some(opts->source_tree, &opts->pathspec);
- for (pos = 0; opts->pathspec[pos]; pos++)
- ;
- ps_matched = xcalloc(1, pos);
+ ps_matched = xcalloc(1, opts->pathspec.nr);
/*
* Make sure all pathspecs participated in locating the paths
* match_pathspec() for _all_ entries when
* opts->source_tree != NULL.
*/
- if (match_pathspec(opts->pathspec, ce->name, ce_namelen(ce),
+ if (match_pathspec_depth(&opts->pathspec, ce->name, ce_namelen(ce),
0, ps_matched))
ce->ce_flags |= CE_MATCHED;
}
- if (report_path_error(ps_matched, opts->pathspec, opts->prefix)) {
+ if (report_path_error(ps_matched, &opts->pathspec, opts->prefix)) {
free(ps_matched);
return 1;
}
static int checkout_branch(struct checkout_opts *opts,
struct branch_info *new)
{
- if (opts->pathspec)
+ if (opts->pathspec.nr)
die(_("paths cannot be used with switching branches"));
if (opts->patch_mode)
}
if (argc) {
- opts.pathspec = get_pathspec(prefix, argv);
+ parse_pathspec(&opts.pathspec, 0,
+ opts.patch_mode ? PATHSPEC_PREFIX_ORIGIN : 0,
+ prefix, argv);
- if (!opts.pathspec)
+ if (!opts.pathspec.nr)
die(_("invalid path specification"));
/*
strbuf_release(&buf);
}
- if (opts.patch_mode || opts.pathspec)
+ if (opts.patch_mode || opts.pathspec.nr)
return checkout_paths(&opts, new.name);
else
return checkout_branch(&opts, &new);
#include "quote.h"
#include "column.h"
#include "color.h"
+#include "pathspec.h"
static int force = -1; /* unset */
static int interactive;
int rm_flags = REMOVE_DIR_KEEP_NESTED_GIT;
struct strbuf abs_path = STRBUF_INIT;
struct dir_struct dir;
- static const char **pathspec;
+ struct pathspec pathspec;
struct strbuf buf = STRBUF_INIT;
struct string_list exclude_list = STRING_LIST_INIT_NODUP;
struct exclude_list *el;
struct string_list_item *item;
const char *qname;
- char *seen = NULL;
struct option options[] = {
OPT__QUIET(&quiet, N_("do not print names of files removed")),
OPT__DRY_RUN(&dry_run, N_("dry run")),
for (i = 0; i < exclude_list.nr; i++)
add_exclude(exclude_list.items[i].string, "", 0, el, -(i+1));
- pathspec = get_pathspec(prefix, argv);
+ parse_pathspec(&pathspec, 0,
+ PATHSPEC_PREFER_CWD,
+ prefix, argv);
- fill_directory(&dir, pathspec);
-
- if (pathspec)
- seen = xmalloc(argc > 0 ? argc : 1);
+ fill_directory(&dir, &pathspec);
for (i = 0; i < dir.nr; i++) {
struct dir_entry *ent = dir.entries[i];
if (lstat(ent->name, &st))
die_errno("Cannot lstat '%s'", ent->name);
- if (pathspec) {
- memset(seen, 0, argc > 0 ? argc : 1);
- matches = match_pathspec(pathspec, ent->name, len,
- 0, seen);
- }
+ if (pathspec.nr)
+ matches = match_pathspec_depth(&pathspec, ent->name,
+ len, 0, NULL);
if (S_ISDIR(st.st_mode)) {
if (remove_directories || (matches == MATCHED_EXACTLY)) {
string_list_append(&del_list, rel);
}
} else {
- if (pathspec && !matches)
+ if (pathspec.nr && !matches)
continue;
rel = relative_path(ent->name, prefix, &buf);
string_list_append(&del_list, rel);
}
strbuf_reset(&abs_path);
}
- free(seen);
strbuf_release(&abs_path);
strbuf_release(&buf);
#include "column.h"
#include "sequencer.h"
#include "notes-utils.h"
+#include "mailmap.h"
static const char * const builtin_commit_usage[] = {
N_("git commit [options] [--] <pathspec>..."),
s->whence = whence;
}
+static void status_init_config(struct wt_status *s, config_fn_t fn)
+{
+ wt_status_prepare(s);
+ gitmodules_config();
+ git_config(fn, s);
+ determine_whence(s);
+ s->hints = advice_status_hints; /* must come after git_config() */
+}
+
static void rollback_index_files(void)
{
switch (commit_style) {
* and return the paths that match the given pattern in list.
*/
static int list_paths(struct string_list *list, const char *with_tree,
- const char *prefix, const char **pattern)
+ const char *prefix, const struct pathspec *pattern)
{
int i;
char *m;
- if (!pattern)
+ if (!pattern->nr)
return 0;
- for (i = 0; pattern[i]; i++)
- ;
- m = xcalloc(1, i);
+ m = xcalloc(1, pattern->nr);
if (with_tree) {
char *max_prefix = common_prefix(pattern);
if (ce->ce_flags & CE_UPDATE)
continue;
- if (!match_pathspec(pattern, ce->name, ce_namelen(ce), 0, m))
+ if (!match_pathspec_depth(pattern, ce->name, ce_namelen(ce), 0, m))
continue;
item = string_list_insert(list, ce->name);
if (ce_skip_worktree(ce))
{
int fd;
struct string_list partial;
- const char **pathspec = NULL;
+ struct pathspec pathspec;
char *old_index_env = NULL;
int refresh_flags = REFRESH_QUIET;
if (is_status)
refresh_flags |= REFRESH_UNMERGED;
+ parse_pathspec(&pathspec, 0,
+ PATHSPEC_PREFER_FULL,
+ prefix, argv);
- if (*argv)
- pathspec = get_pathspec(prefix, argv);
-
- if (read_cache_preload(pathspec) < 0)
+ if (read_cache_preload(&pathspec) < 0)
die(_("index file corrupt"));
if (interactive) {
* (A) if all goes well, commit the real index;
* (B) on failure, rollback the real index.
*/
- if (all || (also && pathspec && *pathspec)) {
+ if (all || (also && pathspec.nr)) {
fd = hold_locked_index(&index_lock, 1);
- add_files_to_cache(also ? prefix : NULL, pathspec, 0);
+ add_files_to_cache(also ? prefix : NULL, &pathspec, 0);
refresh_cache_or_die(refresh_flags);
update_main_cache_tree(WRITE_TREE_SILENT);
if (write_cache(fd, active_cache, active_nr) ||
* and create commit from the_index.
* We still need to refresh the index here.
*/
- if (!only && (!pathspec || !*pathspec)) {
+ if (!only && !pathspec.nr) {
fd = hold_locked_index(&index_lock, 1);
refresh_cache_or_die(refresh_flags);
if (active_cache_changed) {
memset(&partial, 0, sizeof(partial));
partial.strdup_strings = 1;
- if (list_paths(&partial, !current_head ? NULL : "HEAD", prefix, pathspec))
+ if (list_paths(&partial, !current_head ? NULL : "HEAD", prefix, &pathspec))
exit(1);
discard_cache();
const char *hook_arg2 = NULL;
int ident_shown = 0;
int clean_message_contents = (cleanup_mode != CLEANUP_NONE);
+ int old_display_comment_prefix;
/* This checks and barfs if author is badly specified */
determine_author_info(author_ident);
if (s->fp == NULL)
die_errno(_("could not open '%s'"), git_path(commit_editmsg));
+ /* Ignore status.displayCommentPrefix: we do need comments in COMMIT_EDITMSG. */
+ old_display_comment_prefix = s->display_comment_prefix;
+ s->display_comment_prefix = 1;
+
+ /*
+ * Most hints are counter-productive when the commit has
+ * already started.
+ */
+ s->hints = 0;
+
if (clean_message_contents)
stripspace(&sb, 0);
*/
if (!commitable && whence != FROM_MERGE && !allow_empty &&
!(amend && is_a_merge(current_head))) {
+ s->display_comment_prefix = old_display_comment_prefix;
run_status(stdout, index_file, prefix, 0, s);
if (amend)
fputs(_(empty_amend_advice), stderr);
struct rev_info revs;
struct commit *commit;
struct strbuf buf = STRBUF_INIT;
+ struct string_list mailmap = STRING_LIST_INIT_NODUP;
const char *av[20];
int ac = 0;
av[++ac] = buf.buf;
av[++ac] = NULL;
setup_revisions(ac, av, &revs, NULL);
+ revs.mailmap = &mailmap;
+ read_mailmap(revs.mailmap, NULL);
+
prepare_revision_walk(&revs);
commit = get_revision(&revs);
if (commit) {
struct pretty_print_context ctx = {0};
ctx.date_mode = DATE_NORMAL;
strbuf_release(&buf);
- format_commit_message(commit, "%an <%ae>", &buf, &ctx);
+ format_commit_message(commit, "%aN <%aE>", &buf, &ctx);
+ clear_mailmap(&mailmap);
return strbuf_detach(&buf, NULL);
}
die(_("No existing author found with '%s'"), name);
s->use_color = git_config_colorbool(k, v);
return 0;
}
+ if (!strcmp(k, "status.displaycommentprefix")) {
+ s->display_comment_prefix = git_config_bool(k, v);
+ return 0;
+ }
if (!prefixcmp(k, "status.color.") || !prefixcmp(k, "color.status.")) {
int slot = parse_status_slot(k, 13);
if (slot < 0)
if (argc == 2 && !strcmp(argv[1], "-h"))
usage_with_options(builtin_status_usage, builtin_status_options);
- wt_status_prepare(&s);
- gitmodules_config();
- git_config(git_status_config, &s);
- determine_whence(&s);
+ status_init_config(&s, git_status_config);
argc = parse_options(argc, argv, prefix,
builtin_status_options,
builtin_status_usage, 0);
handle_untracked_files_arg(&s);
if (show_ignored_in_status)
s.show_ignored_files = 1;
- if (*argv)
- s.pathspec = get_pathspec(prefix, argv);
+ parse_pathspec(&s.pathspec, 0,
+ PATHSPEC_PREFER_FULL,
+ prefix, argv);
- read_cache_preload(s.pathspec);
- refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, s.pathspec, NULL, NULL);
+ read_cache_preload(&s.pathspec);
+ refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, &s.pathspec, NULL, NULL);
fd = hold_locked_index(&index_lock, 0);
if (0 <= fd)
if (argc == 2 && !strcmp(argv[1], "-h"))
usage_with_options(builtin_commit_usage, builtin_commit_options);
- wt_status_prepare(&s);
- gitmodules_config();
- git_config(git_commit_config, &s);
+ status_init_config(&s, git_commit_config);
status_format = STATUS_FORMAT_NONE; /* Ignore status.short */
- determine_whence(&s);
s.colopts = 0;
if (get_sha1("HEAD", sha1))
!current_head
? NULL
: current_head->object.sha1,
- 0);
+ 0, NULL);
nl = strchr(sb.buf, '\n');
if (nl)
#include "cache.h"
#include "color.h"
#include "parse-options.h"
+#include "urlmatch.h"
static const char *const builtin_config_usage[] = {
N_("git config [options]"),
#define ACTION_SET_ALL (1<<12)
#define ACTION_GET_COLOR (1<<13)
#define ACTION_GET_COLORBOOL (1<<14)
+#define ACTION_GET_URLMATCH (1<<15)
#define TYPE_BOOL (1<<0)
#define TYPE_INT (1<<1)
OPT_BIT(0, "get", &actions, N_("get value: name [value-regex]"), ACTION_GET),
OPT_BIT(0, "get-all", &actions, N_("get all values: key [value-regex]"), ACTION_GET_ALL),
OPT_BIT(0, "get-regexp", &actions, N_("get values for regexp: name-regex [value-regex]"), ACTION_GET_REGEXP),
+ OPT_BIT(0, "get-urlmatch", &actions, N_("get value specific for the URL: section[.var] URL"), ACTION_GET_URLMATCH),
OPT_BIT(0, "replace-all", &actions, N_("replace all matching variables: name value [value_regex]"), ACTION_REPLACE_ALL),
OPT_BIT(0, "add", &actions, N_("add a new variable: name value"), ACTION_ADD),
OPT_BIT(0, "unset", &actions, N_("remove a variable: name [value-regex]"), ACTION_UNSET),
int alloc;
};
-static int collect_config(const char *key_, const char *value_, void *cb)
+static int format_config(struct strbuf *buf, const char *key_, const char *value_)
{
- struct strbuf_list *values = cb;
- struct strbuf *buf;
- char value[256];
- const char *vptr = value;
int must_free_vptr = 0;
int must_print_delim = 0;
+ char value[256];
+ const char *vptr = value;
- if (!use_key_regexp && strcmp(key_, key))
- return 0;
- if (use_key_regexp && regexec(key_regexp, key_, 0, NULL, 0))
- return 0;
- if (regexp != NULL &&
- (do_not_match ^ !!regexec(regexp, (value_?value_:""), 0, NULL, 0)))
- return 0;
-
- ALLOC_GROW(values->items, values->nr + 1, values->alloc);
- buf = &values->items[values->nr++];
strbuf_init(buf, 0);
if (show_keys) {
must_print_delim = 1;
}
if (types == TYPE_INT)
- sprintf(value, "%d", git_config_int(key_, value_?value_:""));
+ sprintf(value, "%"PRId64,
+ git_config_int64(key_, value_ ? value_ : ""));
else if (types == TYPE_BOOL)
vptr = git_config_bool(key_, value_) ? "true" : "false";
else if (types == TYPE_BOOL_OR_INT) {
strbuf_addch(buf, term);
if (must_free_vptr)
- /* If vptr must be freed, it's a pointer to a
- * dynamically allocated buffer, it's safe to cast to
- * const.
- */
free((char *)vptr);
-
return 0;
}
+static int collect_config(const char *key_, const char *value_, void *cb)
+{
+ struct strbuf_list *values = cb;
+
+ if (!use_key_regexp && strcmp(key_, key))
+ return 0;
+ if (use_key_regexp && regexec(key_regexp, key_, 0, NULL, 0))
+ return 0;
+ if (regexp != NULL &&
+ (do_not_match ^ !!regexec(regexp, (value_?value_:""), 0, NULL, 0)))
+ return 0;
+
+ ALLOC_GROW(values->items, values->nr + 1, values->alloc);
+
+ return format_config(&values->items[values->nr++], key_, value_);
+}
+
static int get_value(const char *key_, const char *regex_)
{
int ret = CONFIG_GENERIC_ERROR;
else {
normalized = xmalloc(64);
if (types == TYPE_INT) {
- int v = git_config_int(key, value);
- sprintf(normalized, "%d", v);
+ int64_t v = git_config_int64(key, value);
+ sprintf(normalized, "%"PRId64, v);
}
else if (types == TYPE_BOOL)
sprintf(normalized, "%s",
die("writing config blobs is not supported");
}
+struct urlmatch_current_candidate_value {
+ char value_is_null;
+ struct strbuf value;
+};
+
+static int urlmatch_collect_fn(const char *var, const char *value, void *cb)
+{
+ struct string_list *values = cb;
+ struct string_list_item *item = string_list_insert(values, var);
+ struct urlmatch_current_candidate_value *matched = item->util;
+
+ if (!matched) {
+ matched = xmalloc(sizeof(*matched));
+ strbuf_init(&matched->value, 0);
+ item->util = matched;
+ } else {
+ strbuf_reset(&matched->value);
+ }
+
+ if (value) {
+ strbuf_addstr(&matched->value, value);
+ matched->value_is_null = 0;
+ } else {
+ matched->value_is_null = 1;
+ }
+ return 0;
+}
+
+static char *dup_downcase(const char *string)
+{
+ char *result;
+ size_t len, i;
+
+ len = strlen(string);
+ result = xmalloc(len + 1);
+ for (i = 0; i < len; i++)
+ result[i] = tolower(string[i]);
+ result[i] = '\0';
+ return result;
+}
+
+static int get_urlmatch(const char *var, const char *url)
+{
+ char *section_tail;
+ struct string_list_item *item;
+ struct urlmatch_config config = { STRING_LIST_INIT_DUP };
+ struct string_list values = STRING_LIST_INIT_DUP;
+
+ config.collect_fn = urlmatch_collect_fn;
+ config.cascade_fn = NULL;
+ config.cb = &values;
+
+ if (!url_normalize(url, &config.url))
+ die("%s", config.url.err);
+
+ config.section = dup_downcase(var);
+ section_tail = strchr(config.section, '.');
+ if (section_tail) {
+ *section_tail = '\0';
+ config.key = section_tail + 1;
+ show_keys = 0;
+ } else {
+ config.key = NULL;
+ show_keys = 1;
+ }
+
+ git_config_with_options(urlmatch_config_entry, &config,
+ given_config_file, NULL, respect_includes);
+
+ for_each_string_list_item(item, &values) {
+ struct urlmatch_current_candidate_value *matched = item->util;
+ struct strbuf key = STRBUF_INIT;
+ struct strbuf buf = STRBUF_INIT;
+
+ strbuf_addstr(&key, item->string);
+ format_config(&buf, key.buf,
+ matched->value_is_null ? NULL : matched->value.buf);
+ fwrite(buf.buf, 1, buf.len, stdout);
+ strbuf_release(&key);
+ strbuf_release(&buf);
+
+ strbuf_release(&matched->value);
+ }
+ string_list_clear(&config.vars, 1);
+ string_list_clear(&values, 1);
+ free(config.url.url);
+
+ free((void *)config.section);
+ return 0;
+}
+
int cmd_config(int argc, const char **argv, const char *prefix)
{
int nongit = !startup_info->have_repository;
check_argc(argc, 1, 2);
return get_value(argv[0], argv[1]);
}
+ else if (actions == ACTION_GET_URLMATCH) {
+ check_argc(argc, 2, 2);
+ return get_urlmatch(argv[0], argv[1]);
+ }
else if (actions == ACTION_UNSET) {
check_blob_write();
check_argc(argc, 1, 2);
#define MAX_TAGS (FLAG_BITS - 1)
static const char * const describe_usage[] = {
- N_("git describe [options] <committish>*"),
+ N_("git describe [options] <commit-ish>*"),
N_("git describe [options] --dirty"),
NULL
};
}
describe("HEAD", 1);
} else if (dirty) {
- die(_("--dirty is incompatible with committishes"));
+ die(_("--dirty is incompatible with commit-ishes"));
} else {
while (argc-- > 0) {
describe(*argv++, argc == 0);
(rev.diffopt.output_format & DIFF_FORMAT_PATCH))
rev.combine_merges = rev.dense_combined_merges = 1;
- if (read_cache_preload(rev.diffopt.pathspec.raw) < 0) {
+ if (read_cache_preload(&rev.diffopt.pathspec) < 0) {
perror("read_cache_preload");
return -1;
}
usage(diff_cache_usage);
if (!cached) {
setup_work_tree();
- if (read_cache_preload(rev.diffopt.pathspec.raw) < 0) {
+ if (read_cache_preload(&rev.diffopt.pathspec) < 0) {
perror("read_cache_preload");
return -1;
}
usage(builtin_diff_usage);
if (!cached) {
setup_work_tree();
- if (read_cache_preload(revs->diffopt.pathspec.raw) < 0) {
+ if (read_cache_preload(&revs->diffopt.pathspec) < 0) {
perror("read_cache_preload");
return -1;
}
revs->combine_merges = revs->dense_combined_merges = 1;
setup_work_tree();
- if (read_cache_preload(revs->diffopt.pathspec.raw) < 0) {
+ if (read_cache_preload(&revs->diffopt.pathspec) < 0) {
perror("read_cache_preload");
return -1;
}
}
}
if (rev.prune_data.nr) {
+ /* builtin_diff_b_f() */
+ GUARD_PATHSPEC(&rev.prune_data, PATHSPEC_FROMTOP | PATHSPEC_LITERAL);
if (!path)
path = rev.prune_data.items[0].match;
paths += rev.prune_data.nr;
static int use_done_feature;
static int no_data;
static int full_tree;
+static struct string_list extra_refs = STRING_LIST_INIT_NODUP;
static int parse_opt_signed_tag_mode(const struct option *opt,
const char *arg, int unset)
(int)message_size, (int)message_size, message ? message : "");
}
-static void get_tags_and_duplicates(struct rev_cmdline_info *info,
- struct string_list *extra_refs)
+static struct commit *get_commit(struct rev_cmdline_entry *e, char *full_name)
+{
+ switch (e->item->type) {
+ case OBJ_COMMIT:
+ return (struct commit *)e->item;
+ case OBJ_TAG: {
+ struct tag *tag = (struct tag *)e->item;
+
+ /* handle nested tags */
+ while (tag && tag->object.type == OBJ_TAG) {
+ parse_object(tag->object.sha1);
+ string_list_append(&extra_refs, full_name)->util = tag;
+ tag = (struct tag *)tag->tagged;
+ }
+ if (!tag)
+ die("Tag %s points nowhere?", e->name);
+ return (struct commit *)tag;
+ break;
+ }
+ default:
+ return NULL;
+ }
+}
+
+static void get_tags_and_duplicates(struct rev_cmdline_info *info)
{
- struct tag *tag;
int i;
for (i = 0; i < info->nr; i++) {
if (dwim_ref(e->name, strlen(e->name), sha1, &full_name) != 1)
continue;
- switch (e->item->type) {
- case OBJ_COMMIT:
- commit = (struct commit *)e->item;
- break;
- case OBJ_TAG:
- tag = (struct tag *)e->item;
-
- /* handle nested tags */
- while (tag && tag->object.type == OBJ_TAG) {
- parse_object(tag->object.sha1);
- string_list_append(extra_refs, full_name)->util = tag;
- tag = (struct tag *)tag->tagged;
- }
- if (!tag)
- die ("Tag %s points nowhere?", e->name);
- switch(tag->object.type) {
- case OBJ_COMMIT:
- commit = (struct commit *)tag;
- break;
- case OBJ_BLOB:
- export_blob(tag->object.sha1);
- continue;
- default: /* OBJ_TAG (nested tags) is already handled */
- warning("Tag points to object of unexpected type %s, skipping.",
- typename(tag->object.type));
- continue;
- }
- break;
- default:
+ commit = get_commit(e, full_name);
+ if (!commit) {
warning("%s: Unexpected object of type %s, skipping.",
e->name,
typename(e->item->type));
continue;
}
+ switch(commit->object.type) {
+ case OBJ_COMMIT:
+ break;
+ case OBJ_BLOB:
+ export_blob(commit->object.sha1);
+ continue;
+ default: /* OBJ_TAG (nested tags) is already handled */
+ warning("Tag points to object of unexpected type %s, skipping.",
+ typename(commit->object.type));
+ continue;
+ }
+
/*
* This ref will not be updated through a commit, lets make
* sure it gets properly updated eventually.
*/
if (commit->util || commit->object.flags & SHOWN)
- string_list_append(extra_refs, full_name)->util = commit;
+ string_list_append(&extra_refs, full_name)->util = commit;
if (!commit->util)
commit->util = full_name;
}
}
-static void handle_tags_and_duplicates(struct string_list *extra_refs)
+static void handle_tags_and_duplicates(void)
{
struct commit *commit;
int i;
- for (i = extra_refs->nr - 1; i >= 0; i--) {
- const char *name = extra_refs->items[i].string;
- struct object *object = extra_refs->items[i].util;
+ for (i = extra_refs.nr - 1; i >= 0; i--) {
+ const char *name = extra_refs.items[i].string;
+ struct object *object = extra_refs.items[i].util;
switch (object->type) {
case OBJ_TAG:
handle_tag(name, (struct tag *)object);
{
struct rev_info revs;
struct object_array commits = OBJECT_ARRAY_INIT;
- struct string_list extra_refs = STRING_LIST_INIT_NODUP;
struct commit *commit;
char *export_filename = NULL, *import_filename = NULL;
uint32_t lastimportid;
if (import_filename && revs.prune_data.nr)
full_tree = 1;
- get_tags_and_duplicates(&revs.cmdline, &extra_refs);
+ get_tags_and_duplicates(&revs.cmdline);
if (prepare_revision_walk(&revs))
die("revision walk setup failed");
}
}
- handle_tags_and_duplicates(&extra_refs);
+ handle_tags_and_duplicates();
if (export_filename && lastimportid != last_idnum)
export_marks(export_filename);
#include "builtin.h"
#include "pkt-line.h"
#include "fetch-pack.h"
+#include "remote.h"
+#include "connect.h"
static const char fetch_pack_usage[] =
"git fetch-pack [--all] [--stdin] [--quiet|-q] [--keep|-k] [--thin] "
pack_lockfile_ptr = &pack_lockfile;
continue;
}
+ if (!strcmp("--check-self-contained-and-connected", arg)) {
+ args.check_self_contained_and_connected = 1;
+ continue;
+ }
usage(fetch_pack_usage);
}
printf("lock %s\n", pack_lockfile);
fflush(stdout);
}
+ if (args.check_self_contained_and_connected &&
+ args.self_contained_and_connected) {
+ printf("connectivity-ok\n");
+ fflush(stdout);
+ }
close(fd[0]);
close(fd[1]);
if (finish_connect(conn))
TAGS_SET = 2
};
-static int all, append, dry_run, force, keep, multiple, prune, update_head_ok, verbosity;
+static int fetch_prune_config = -1; /* unspecified */
+static int prune = -1; /* unspecified */
+#define PRUNE_BY_DEFAULT 0 /* do we prune by default? */
+
+static int all, append, dry_run, force, keep, multiple, update_head_ok, verbosity;
static int progress = -1, recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
static int tags = TAGS_DEFAULT, unshallow;
static const char *depth;
static const char *upload_pack;
static struct strbuf default_rla = STRBUF_INIT;
-static struct transport *transport;
+static struct transport *gtransport;
+static struct transport *gsecondary;
static const char *submodule_prefix = "";
static const char *recurse_submodules_default;
return 0;
}
+static int git_fetch_config(const char *k, const char *v, void *cb)
+{
+ if (!strcmp(k, "fetch.prune")) {
+ fetch_prune_config = git_config_bool(k, v);
+ return 0;
+ }
+ return 0;
+}
+
static struct option builtin_fetch_options[] = {
OPT__VERBOSITY(&verbosity),
OPT_BOOL(0, "all", &all,
static void unlock_pack(void)
{
- if (transport)
- transport_unlock_pack(transport);
+ if (gtransport)
+ transport_unlock_pack(gtransport);
+ if (gsecondary)
+ transport_unlock_pack(gsecondary);
}
static void unlock_pack_on_signal(int signo)
rla = default_rla.buf;
snprintf(msg, sizeof(msg), "%s: %s", rla, action);
lock = lock_any_ref_for_update(ref->name,
- check_old ? ref->old_sha1 : NULL, 0);
+ check_old ? ref->old_sha1 : NULL,
+ 0, NULL);
if (!lock)
return errno == ENOTDIR ? STORE_REF_ERROR_DF_CONFLICT :
STORE_REF_ERROR_OTHER;
return 0;
}
+static void set_option(struct transport *transport, const char *name, const char *value)
+{
+ int r = transport_set_option(transport, name, value);
+ if (r < 0)
+ die(_("Option \"%s\" value \"%s\" is not valid for %s"),
+ name, value, transport->url);
+ if (r > 0)
+ warning(_("Option \"%s\" is ignored for %s\n"),
+ name, transport->url);
+}
+
+static struct transport *prepare_transport(struct remote *remote)
+{
+ struct transport *transport;
+ transport = transport_get(remote, NULL);
+ transport_set_verbosity(transport, verbosity, progress);
+ if (upload_pack)
+ set_option(transport, TRANS_OPT_UPLOADPACK, upload_pack);
+ if (keep)
+ set_option(transport, TRANS_OPT_KEEP, "yes");
+ if (depth)
+ set_option(transport, TRANS_OPT_DEPTH, depth);
+ return transport;
+}
+
+static void backfill_tags(struct transport *transport, struct ref *ref_map)
+{
+ if (transport->cannot_reuse) {
+ gsecondary = prepare_transport(transport->remote);
+ transport = gsecondary;
+ }
+
+ transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL);
+ transport_set_option(transport, TRANS_OPT_DEPTH, "0");
+ fetch_refs(transport, ref_map);
+
+ if (gsecondary) {
+ transport_disconnect(gsecondary);
+ gsecondary = NULL;
+ }
+}
+
static int do_fetch(struct transport *transport,
struct refspec *refs, int ref_count)
{
goto cleanup;
}
if (prune) {
- /* If --tags was specified, pretend the user gave us the canonical tags refspec */
+ /*
+ * If --tags was specified, pretend that the user gave us
+ * the canonical tags refspec
+ */
if (tags == TAGS_SET) {
const char *tags_str = "refs/tags/*:refs/tags/*";
struct refspec *tags_refspec, *refspec;
struct ref **tail = &ref_map;
ref_map = NULL;
find_non_local_tags(transport, &ref_map, &tail);
- if (ref_map) {
- transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL);
- transport_set_option(transport, TRANS_OPT_DEPTH, "0");
- fetch_refs(transport, ref_map);
- }
+ if (ref_map)
+ backfill_tags(transport, ref_map);
free_refs(ref_map);
}
return retcode;
}
-static void set_option(const char *name, const char *value)
-{
- int r = transport_set_option(transport, name, value);
- if (r < 0)
- die(_("Option \"%s\" value \"%s\" is not valid for %s"),
- name, value, transport->url);
- if (r > 0)
- warning(_("Option \"%s\" is ignored for %s\n"),
- name, transport->url);
-}
-
static int get_one_remote_for_fetch(struct remote *remote, void *priv)
{
struct string_list *list = priv;
{
if (dry_run)
argv_array_push(argv, "--dry-run");
- if (prune)
+ if (prune > 0)
argv_array_push(argv, "--prune");
if (update_head_ok)
argv_array_push(argv, "--update-head-ok");
die(_("No remote repository specified. Please, specify either a URL or a\n"
"remote name from which new revisions should be fetched."));
- transport = transport_get(remote, NULL);
- transport_set_verbosity(transport, verbosity, progress);
- if (upload_pack)
- set_option(TRANS_OPT_UPLOADPACK, upload_pack);
- if (keep)
- set_option(TRANS_OPT_KEEP, "yes");
- if (depth)
- set_option(TRANS_OPT_DEPTH, depth);
+ gtransport = prepare_transport(remote);
+
+ if (prune < 0) {
+ /* no command line request */
+ if (0 <= gtransport->remote->prune)
+ prune = gtransport->remote->prune;
+ else if (0 <= fetch_prune_config)
+ prune = fetch_prune_config;
+ else
+ prune = PRUNE_BY_DEFAULT;
+ }
if (argc > 0) {
int j = 0;
sigchain_push_common(unlock_pack_on_signal);
atexit(unlock_pack);
refspec = parse_fetch_refspec(ref_nr, refs);
- exit_code = do_fetch(transport, refspec, ref_nr);
+ exit_code = do_fetch(gtransport, refspec, ref_nr);
free_refspec(ref_nr, refspec);
- transport_disconnect(transport);
- transport = NULL;
+ transport_disconnect(gtransport);
+ gtransport = NULL;
return exit_code;
}
for (i = 1; i < argc; i++)
strbuf_addf(&default_rla, " %s", argv[i]);
+ git_config(git_fetch_config, NULL);
+
argc = parse_options(argc, argv, prefix,
builtin_fetch_options, builtin_fetch_usage, 0);
#define REACHABLE 0x0001
#define SEEN 0x0002
+#define HAS_OBJ 0x0004
static int show_root;
static int show_tags;
if (obj->flags & REACHABLE)
return 0;
obj->flags |= REACHABLE;
- if (!obj->parsed) {
+ if (!(obj->flags & HAS_OBJ)) {
if (parent && !has_sha1_file(obj->sha1)) {
printf("broken link from %7s %s\n",
typename(parent->type), sha1_to_hex(parent->sha1));
struct tree *tree = NULL;
if (obj->type == OBJ_TREE) {
- obj->parsed = 0;
tree = (struct tree *)obj;
if (parse_tree(tree) < 0)
return 1; /* error already displayed */
}
result = fsck_walk(obj, mark_object, obj);
- if (tree) {
- free(tree->buffer);
- tree->buffer = NULL;
- }
+ if (tree)
+ free_tree_buffer(tree);
return result;
}
* except if it was in a pack-file and we didn't
* do a full fsck
*/
- if (!obj->parsed) {
+ if (!(obj->flags & HAS_OBJ)) {
if (has_sha1_pack(obj->sha1))
return; /* it is in pack - forget about it */
printf("missing %s %s\n", typename(obj->type), sha1_to_hex(obj->sha1));
if (obj->type == OBJ_TREE) {
struct tree *item = (struct tree *) obj;
- free(item->buffer);
- item->buffer = NULL;
+ free_tree_buffer(item);
}
if (obj->type == OBJ_COMMIT) {
return error("%s: object corrupt or missing",
sha1_to_hex(sha1));
}
+ obj->flags |= HAS_OBJ;
return fsck_obj(obj);
}
errors_found |= ERROR_OBJECT;
return error("%s: object corrupt or missing", sha1_to_hex(sha1));
}
+ obj->flags = HAS_OBJ;
return fsck_obj(obj);
}
#include "grep.h"
#include "quote.h"
#include "dir.h"
+#include "pathspec.h"
static char const * const grep_usage[] = {
N_("git grep [options] [-e] <pattern> [<rev>...] [[--] <path>...]"),
if (exc_std)
setup_standard_excludes(&dir);
- fill_directory(&dir, pathspec->raw);
+ fill_directory(&dir, pathspec);
for (i = 0; i < dir.nr; i++) {
const char *name = dir.entries[i]->name;
int namelen = strlen(name);
const char *show_in_pager = NULL, *default_pager = "dummy";
struct grep_opt opt;
struct object_array list = OBJECT_ARRAY_INIT;
- const char **paths = NULL;
struct pathspec pathspec;
struct string_list path_list = STRING_LIST_INIT_NODUP;
int i;
verify_filename(prefix, argv[j], j == i);
}
- paths = get_pathspec(prefix, argv + i);
- init_pathspec(&pathspec, paths);
+ parse_pathspec(&pathspec, 0,
+ PATHSPEC_PREFER_CWD |
+ (opt.max_depth != -1 ? PATHSPEC_MAXDEPTH_VALID : 0),
+ prefix, argv + i);
pathspec.max_depth = opt.max_depth;
pathspec.recursive = 1;
if (obj->type == OBJ_TREE) {
struct tree *item = (struct tree *) obj;
item->buffer = NULL;
+ obj->parsed = 0;
}
if (obj->type == OBJ_COMMIT) {
struct commit *commit = (struct commit *) obj;
init_grep_defaults();
git_config(git_log_config, NULL);
- init_pathspec(&match_all, NULL);
+ memset(&match_all, 0, sizeof(match_all));
init_revisions(&rev, prefix);
rev.diff = 1;
rev.always_show_header = 1;
#include "parse-options.h"
#include "resolve-undo.h"
#include "string-list.h"
+#include "pathspec.h"
static int abbrev;
static int show_deleted;
static const char *prefix;
static int max_prefix_len;
static int prefix_len;
-static const char **pathspec;
+static struct pathspec pathspec;
static int error_unmatch;
static char *ps_matched;
static const char *with_tree;
if (len >= ent->len)
die("git ls-files: internal error - directory entry not superset of prefix");
- if (!match_pathspec(pathspec, ent->name, ent->len, len, ps_matched))
+ if (!match_pathspec_depth(&pathspec, ent->name, ent->len, len, ps_matched))
return;
fputs(tag, stdout);
if (len >= ce_namelen(ce))
die("git ls-files: internal error - cache entry not superset of prefix");
- if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), len, ps_matched))
+ if (!match_pathspec_depth(&pathspec, ce->name, ce_namelen(ce), len, ps_matched))
return;
if (tag && *tag && show_valid_bit &&
len = strlen(path);
if (len < max_prefix_len)
continue; /* outside of the prefix */
- if (!match_pathspec(pathspec, path, len, max_prefix_len, ps_matched))
+ if (!match_pathspec_depth(&pathspec, path, len, max_prefix_len, ps_matched))
continue; /* uninterested */
for (i = 0; i < 3; i++) {
if (!ui->mode[i])
/* For cached/deleted files we don't need to even do the readdir */
if (show_others || show_killed) {
- fill_directory(dir, pathspec);
+ if (!show_others)
+ dir->flags |= DIR_COLLECT_KILLED_ONLY;
+ fill_directory(dir, &pathspec);
if (show_others)
show_other_files(dir);
if (show_killed)
active_nr = last;
}
-static void strip_trailing_slash_from_submodules(void)
-{
- const char **p;
-
- for (p = pathspec; *p != NULL; p++) {
- int len = strlen(*p), pos;
-
- if (len < 1 || (*p)[len - 1] != '/')
- continue;
- pos = cache_name_pos(*p, len - 1);
- if (pos >= 0 && S_ISGITLINK(active_cache[pos]->ce_mode))
- *p = xstrndup(*p, len - 1);
- }
-}
-
/*
* Read the tree specified with --with-tree option
* (typically, HEAD) into stage #1 and then
}
if (prefix) {
- static const char *(matchbuf[2]);
- matchbuf[0] = prefix;
- matchbuf[1] = NULL;
- init_pathspec(&pathspec, matchbuf);
- pathspec.items[0].nowildcard_len = pathspec.items[0].len;
+ static const char *(matchbuf[1]);
+ matchbuf[0] = NULL;
+ parse_pathspec(&pathspec, PATHSPEC_ALL_MAGIC,
+ PATHSPEC_PREFER_CWD, prefix, matchbuf);
} else
- init_pathspec(&pathspec, NULL);
+ memset(&pathspec, 0, sizeof(pathspec));
if (read_tree(tree, 1, &pathspec))
die("unable to read tree entries %s", tree_name);
}
}
-int report_path_error(const char *ps_matched, const char **pathspec, const char *prefix)
+int report_path_error(const char *ps_matched,
+ const struct pathspec *pathspec,
+ const char *prefix)
{
/*
* Make sure all pathspec matched; otherwise it is an error.
*/
struct strbuf sb = STRBUF_INIT;
- const char *name;
int num, errors = 0;
- for (num = 0; pathspec[num]; num++) {
+ for (num = 0; num < pathspec->nr; num++) {
int other, found_dup;
if (ps_matched[num])
/*
* The caller might have fed identical pathspec
* twice. Do not barf on such a mistake.
+ * FIXME: parse_pathspec should have eliminated
+ * duplicate pathspec.
*/
for (found_dup = other = 0;
- !found_dup && pathspec[other];
+ !found_dup && other < pathspec->nr;
other++) {
if (other == num || !ps_matched[other])
continue;
- if (!strcmp(pathspec[other], pathspec[num]))
+ if (!strcmp(pathspec->items[other].original,
+ pathspec->items[num].original))
/*
* Ok, we have a match already.
*/
if (found_dup)
continue;
- name = quote_path_relative(pathspec[num], prefix, &sb);
error("pathspec '%s' did not match any file(s) known to git.",
- name);
+ pathspec->items[num].original);
errors++;
}
strbuf_release(&sb);
if (require_work_tree && !is_inside_work_tree())
setup_work_tree();
- pathspec = get_pathspec(prefix, argv);
-
- /* be nice with submodule paths ending in a slash */
- if (pathspec)
- strip_trailing_slash_from_submodules();
+ parse_pathspec(&pathspec, 0,
+ PATHSPEC_PREFER_CWD |
+ PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP,
+ prefix, argv);
/* Find common prefix for all pathspec's */
- max_prefix = common_prefix(pathspec);
+ max_prefix = common_prefix(&pathspec);
max_prefix_len = max_prefix ? strlen(max_prefix) : 0;
/* Treat unmatching pathspec elements as errors */
- if (pathspec && error_unmatch) {
- int num;
- for (num = 0; pathspec[num]; num++)
- ;
- ps_matched = xcalloc(1, num);
- }
+ if (pathspec.nr && error_unmatch)
+ ps_matched = xcalloc(1, pathspec.nr);
if ((dir.flags & DIR_SHOW_IGNORED) && !exc_given)
die("ls-files --ignored needs some exclude pattern");
if (ps_matched) {
int bad;
- bad = report_path_error(ps_matched, pathspec, prefix);
+ bad = report_path_error(ps_matched, &pathspec, prefix);
if (bad)
fprintf(stderr, "Did you forget to 'git add'?\n");
#include "quote.h"
#include "builtin.h"
#include "parse-options.h"
+#include "pathspec.h"
static int line_termination = '\n';
#define LS_RECURSIVE 1
if (ls_options & LS_RECURSIVE)
return 1;
- s = pathspec.raw;
+ s = pathspec._raw;
if (!s)
return 0;
if (get_sha1(argv[0], sha1))
die("Not a valid object name %s", argv[0]);
- init_pathspec(&pathspec, get_pathspec(prefix, argv + 1));
+ /*
+ * show_recursive() rolls its own matching code and is
+ * generally ignorant of 'struct pathspec'. The magic mask
+ * cannot be lifted until it is converted to use
+ * match_pathspec_depth() or tree_entry_interesting()
+ */
+ parse_pathspec(&pathspec, PATHSPEC_GLOB | PATHSPEC_ICASE,
+ PATHSPEC_PREFER_CWD,
+ prefix, argv + 1);
for (i = 0; i < pathspec.nr; i++)
pathspec.items[i].nowildcard_len = pathspec.items[i].len;
pathspec.has_wildcard = 0;
* This could be traditional "merge <msg> HEAD <commit>..." and
* the way we can tell it is to see if the second token is HEAD,
* but some people might have misused the interface and used a
- * committish that is the same as HEAD there instead.
+ * commit-ish that is the same as HEAD there instead.
* Traditional format never would have "-m" so it is an
* additional safety measure to check for it.
*/
#include "cache-tree.h"
#include "string-list.h"
#include "parse-options.h"
+#include "submodule.h"
static const char * const builtin_mv_usage[] = {
N_("git mv [options] <source>... <destination>"),
NULL
};
-static const char **copy_pathspec(const char *prefix, const char **pathspec,
- int count, int base_name)
+static const char **internal_copy_pathspec(const char *prefix,
+ const char **pathspec,
+ int count, int base_name)
{
int i;
const char **result = xmalloc((count + 1) * sizeof(const char *));
int cmd_mv(int argc, const char **argv, const char *prefix)
{
- int i, newfd;
+ int i, newfd, gitmodules_modified = 0;
int verbose = 0, show_only = 0, force = 0, ignore_errors = 0;
struct option builtin_mv_options[] = {
OPT__VERBOSE(&verbose, N_("be verbose")),
OPT_BOOL('k', NULL, &ignore_errors, N_("skip move/rename errors")),
OPT_END(),
};
- const char **source, **destination, **dest_path;
+ const char **source, **destination, **dest_path, **submodule_gitfile;
enum update_mode { BOTH = 0, WORKING_DIRECTORY, INDEX } *modes;
struct stat st;
struct string_list src_for_dst = STRING_LIST_INIT_NODUP;
+ gitmodules_config();
git_config(git_default_config, NULL);
argc = parse_options(argc, argv, prefix, builtin_mv_options,
if (read_cache() < 0)
die(_("index file corrupt"));
- source = copy_pathspec(prefix, argv, argc, 0);
+ source = internal_copy_pathspec(prefix, argv, argc, 0);
modes = xcalloc(argc, sizeof(enum update_mode));
- dest_path = copy_pathspec(prefix, argv + argc, 1, 0);
+ dest_path = internal_copy_pathspec(prefix, argv + argc, 1, 0);
+ submodule_gitfile = xcalloc(argc, sizeof(char *));
if (dest_path[0][0] == '\0')
/* special case: "." was normalized to "" */
- destination = copy_pathspec(dest_path[0], argv, argc, 1);
+ destination = internal_copy_pathspec(dest_path[0], argv, argc, 1);
else if (!lstat(dest_path[0], &st) &&
S_ISDIR(st.st_mode)) {
dest_path[0] = add_slash(dest_path[0]);
- destination = copy_pathspec(dest_path[0], argv, argc, 1);
+ destination = internal_copy_pathspec(dest_path[0], argv, argc, 1);
} else {
if (argc != 1)
die("destination '%s' is not a directory", dest_path[0]);
&& lstat(dst, &st) == 0)
bad = _("cannot move directory over file");
else if (src_is_dir) {
- const char *src_w_slash = add_slash(src);
- int len_w_slash = length + 1;
- int first, last;
-
- modes[i] = WORKING_DIRECTORY;
-
- first = cache_name_pos(src_w_slash, len_w_slash);
- if (first >= 0)
- die (_("Huh? %.*s is in index?"),
- len_w_slash, src_w_slash);
-
- first = -1 - first;
- for (last = first; last < active_nr; last++) {
- const char *path = active_cache[last]->name;
- if (strncmp(path, src_w_slash, len_w_slash))
- break;
- }
- free((char *)src_w_slash);
-
- if (last - first < 1)
- bad = _("source directory is empty");
- else {
- int j, dst_len;
-
- if (last - first > 0) {
- source = xrealloc(source,
- (argc + last - first)
- * sizeof(char *));
- destination = xrealloc(destination,
- (argc + last - first)
- * sizeof(char *));
- modes = xrealloc(modes,
- (argc + last - first)
- * sizeof(enum update_mode));
+ int first = cache_name_pos(src, length);
+ if (first >= 0) {
+ struct strbuf submodule_dotgit = STRBUF_INIT;
+ if (!S_ISGITLINK(active_cache[first]->ce_mode))
+ die (_("Huh? Directory %s is in index and no submodule?"), src);
+ if (!is_staging_gitmodules_ok())
+ die (_("Please, stage your changes to .gitmodules or stash them to proceed"));
+ strbuf_addf(&submodule_dotgit, "%s/.git", src);
+ submodule_gitfile[i] = read_gitfile(submodule_dotgit.buf);
+ if (submodule_gitfile[i])
+ submodule_gitfile[i] = xstrdup(submodule_gitfile[i]);
+ strbuf_release(&submodule_dotgit);
+ } else {
+ const char *src_w_slash = add_slash(src);
+ int last, len_w_slash = length + 1;
+
+ modes[i] = WORKING_DIRECTORY;
+
+ first = cache_name_pos(src_w_slash, len_w_slash);
+ if (first >= 0)
+ die (_("Huh? %.*s is in index?"),
+ len_w_slash, src_w_slash);
+
+ first = -1 - first;
+ for (last = first; last < active_nr; last++) {
+ const char *path = active_cache[last]->name;
+ if (strncmp(path, src_w_slash, len_w_slash))
+ break;
}
+ free((char *)src_w_slash);
+
+ if (last - first < 1)
+ bad = _("source directory is empty");
+ else {
+ int j, dst_len;
- dst = add_slash(dst);
- dst_len = strlen(dst);
-
- for (j = 0; j < last - first; j++) {
- const char *path =
- active_cache[first + j]->name;
- source[argc + j] = path;
- destination[argc + j] =
- prefix_path(dst, dst_len,
- path + length + 1);
- modes[argc + j] = INDEX;
+ if (last - first > 0) {
+ source = xrealloc(source,
+ (argc + last - first)
+ * sizeof(char *));
+ destination = xrealloc(destination,
+ (argc + last - first)
+ * sizeof(char *));
+ modes = xrealloc(modes,
+ (argc + last - first)
+ * sizeof(enum update_mode));
+ }
+
+ dst = add_slash(dst);
+ dst_len = strlen(dst);
+
+ for (j = 0; j < last - first; j++) {
+ const char *path =
+ active_cache[first + j]->name;
+ source[argc + j] = path;
+ destination[argc + j] =
+ prefix_path(dst, dst_len,
+ path + length + 1);
+ modes[argc + j] = INDEX;
+ }
+ argc += last - first;
}
- argc += last - first;
}
} else if (cache_name_pos(src, length) < 0)
bad = _("not under version control");
int pos;
if (show_only || verbose)
printf(_("Renaming %s to %s\n"), src, dst);
- if (!show_only && mode != INDEX &&
- rename(src, dst) < 0 && !ignore_errors)
- die_errno (_("renaming '%s' failed"), src);
+ if (!show_only && mode != INDEX) {
+ if (rename(src, dst) < 0 && !ignore_errors)
+ die_errno (_("renaming '%s' failed"), src);
+ if (submodule_gitfile[i])
+ connect_work_tree_and_git_dir(dst, submodule_gitfile[i]);
+ if (!update_path_in_gitmodules(src, dst))
+ gitmodules_modified = 1;
+ }
if (mode == WORKING_DIRECTORY)
continue;
rename_cache_entry_at(pos, dst);
}
+ if (gitmodules_modified)
+ stage_updated_gitmodules();
+
if (active_cache_changed) {
if (write_cache(newfd, active_cache, active_nr) ||
commit_locked_index(&lock_file))
if (prepare_revision_walk(&revs))
die("revision walk setup failed");
- mark_edges_uninteresting(revs.commits, &revs, show_edge);
+ mark_edges_uninteresting(&revs, show_edge);
traverse_commit_list(&revs, show_commit, show_object, NULL);
if (keep_unreachable)
NULL,
};
-static int thin;
+static int thin = 1;
static int deleterefs;
static const char *receivepack;
static int verbosity;
static int progress = -1;
+static struct push_cas_option cas;
+
static const char **refspec;
static int refspec_nr;
static int refspec_alloc;
if (receivepack)
transport_set_option(transport,
TRANS_OPT_RECEIVEPACK, receivepack);
- if (thin)
- transport_set_option(transport, TRANS_OPT_THIN, "yes");
+ transport_set_option(transport, TRANS_OPT_THIN, thin ? "yes" : NULL);
+
+ if (!is_empty_cas(&cas)) {
+ if (!transport->smart_options)
+ die("underlying transport does not support --%s option",
+ CAS_OPT_NAME);
+ transport->smart_options->cas = &cas;
+ }
if (verbosity > 0)
fprintf(stderr, _("Pushing to %s\n"), transport->url);
OPT_BIT('n' , "dry-run", &flags, N_("dry run"), TRANSPORT_PUSH_DRY_RUN),
OPT_BIT( 0, "porcelain", &flags, N_("machine-readable output"), TRANSPORT_PUSH_PORCELAIN),
OPT_BIT('f', "force", &flags, N_("force updates"), TRANSPORT_PUSH_FORCE),
+ { OPTION_CALLBACK,
+ 0, CAS_OPT_NAME, &cas, N_("refname>:<expect"),
+ N_("require old value of ref to be at this value"),
+ PARSE_OPT_OPTARG, parseopt_push_cas_option },
{ OPTION_CALLBACK, 0, "recurse-submodules", &flags, N_("check"),
N_("control recursive pushing of submodules"),
PARSE_OPT_OPTARG, option_parse_recurse_submodules },
#include "commit.h"
#include "object.h"
#include "remote.h"
+#include "connect.h"
#include "transport.h"
#include "string-list.h"
#include "sha1-array.h"
static int prefer_ofs_delta = 1;
static int auto_update_server_info;
static int auto_gc = 1;
+static int fix_thin = 1;
static const char *head_name;
static void *head_name_to_free;
static int sent_capabilities;
return NULL; /* good */
}
else {
- lock = lock_any_ref_for_update(namespaced_name, old_sha1, 0);
+ lock = lock_any_ref_for_update(namespaced_name, old_sha1,
+ 0, NULL);
if (!lock) {
rp_error("failed to lock %s", name);
return "failed to lock";
keeper[i++] = "--stdin";
if (fsck_objects)
keeper[i++] = "--strict";
- keeper[i++] = "--fix-thin";
+ if (fix_thin)
+ keeper[i++] = "--fix-thin";
keeper[i++] = hdr_arg;
keeper[i++] = keep_arg;
keeper[i++] = NULL;
stateless_rpc = 1;
continue;
}
+ if (!strcmp(arg, "--reject-thin-pack-for-testing")) {
+ fix_thin = 0;
+ continue;
+ }
usage(receive_pack_usage);
}
complete = 0;
}
}
- free(tree->buffer);
- tree->buffer = NULL;
+ free_tree_buffer(tree);
if (complete)
tree->object.flags |= SEEN;
* we take the lock for the ref itself to prevent it from
* getting updated.
*/
- lock = lock_any_ref_for_update(ref, sha1, 0);
+ lock = lock_any_ref_for_update(ref, sha1, 0, NULL);
if (!lock)
return error("cannot lock ref '%s'", ref);
log_file = git_pathdup("logs/%s", ref);
else if (!force)
die("replace ref '%s' already exists", ref);
- lock = lock_any_ref_for_update(ref, prev, 0);
+ lock = lock_any_ref_for_update(ref, prev, 0, NULL);
if (!lock)
die("%s: cannot lock the ref", ref);
if (write_ref_sha1(lock, repl, NULL) < 0)
#include "rerere.h"
#include "xdiff/xdiff.h"
#include "xdiff-interface.h"
+#include "pathspec.h"
static const char * const rerere_usage[] = {
N_("git rerere [clear | forget path... | status | remaining | diff | gc]"),
return rerere(flags);
if (!strcmp(argv[0], "forget")) {
- const char **pathspec;
+ struct pathspec pathspec;
if (argc < 2)
warning("'git rerere forget' without paths is deprecated");
- pathspec = get_pathspec(prefix, argv + 1);
- return rerere_forget(pathspec);
+ parse_pathspec(&pathspec, 0, PATHSPEC_PREFER_CWD,
+ prefix, argv + 1);
+ return rerere_forget(&pathspec);
}
fd = setup_rerere(&merge_rr, flags);
}
}
-static int read_from_tree(const char **pathspec, unsigned char *tree_sha1)
+static int read_from_tree(const struct pathspec *pathspec,
+ unsigned char *tree_sha1)
{
struct diff_options opt;
memset(&opt, 0, sizeof(opt));
- diff_tree_setup_paths(pathspec, &opt);
+ copy_pathspec(&opt.pathspec, pathspec);
opt.output_format = DIFF_FORMAT_CALLBACK;
opt.format_callback = update_index_from_diff;
return 1;
diffcore_std(&opt);
diff_flush(&opt);
- diff_tree_release_paths(&opt);
+ free_pathspec(&opt.pathspec);
return 0;
}
}
-static const char **parse_args(const char **argv, const char *prefix, const char **rev_ret)
+static void parse_args(struct pathspec *pathspec,
+ const char **argv, const char *prefix,
+ int patch_mode,
+ const char **rev_ret)
{
const char *rev = "HEAD";
unsigned char unused[20];
}
}
*rev_ret = rev;
- return argv[0] ? get_pathspec(prefix, argv) : NULL;
+ parse_pathspec(pathspec, 0,
+ PATHSPEC_PREFER_FULL |
+ (patch_mode ? PATHSPEC_PREFIX_ORIGIN : 0),
+ prefix, argv);
}
-static int update_refs(const char *rev, const unsigned char *sha1)
+static int reset_refs(const char *rev, const unsigned char *sha1)
{
int update_ref_status;
struct strbuf msg = STRBUF_INIT;
int patch_mode = 0, unborn;
const char *rev;
unsigned char sha1[20];
- const char **pathspec = NULL;
+ struct pathspec pathspec;
const struct option options[] = {
OPT__QUIET(&quiet, N_("be quiet, only report errors")),
OPT_SET_INT(0, "mixed", &reset_type,
argc = parse_options(argc, argv, prefix, options, git_reset_usage,
PARSE_OPT_KEEP_DASHDASH);
- pathspec = parse_args(argv, prefix, &rev);
+ parse_args(&pathspec, argv, prefix, patch_mode, &rev);
unborn = !strcmp(rev, "HEAD") && get_sha1("HEAD", sha1);
if (unborn) {
/* reset on unborn branch: treat as reset to empty tree */
hashcpy(sha1, EMPTY_TREE_SHA1_BIN);
- } else if (!pathspec) {
+ } else if (!pathspec.nr) {
struct commit *commit;
if (get_sha1_committish(rev, sha1))
die(_("Failed to resolve '%s' as a valid revision."), rev);
if (patch_mode) {
if (reset_type != NONE)
die(_("--patch is incompatible with --{hard,mixed,soft}"));
- return run_add_interactive(sha1_to_hex(sha1), "--patch=reset", pathspec);
+ return run_add_interactive(sha1_to_hex(sha1), "--patch=reset", &pathspec);
}
/* git reset tree [--] paths... can be used to
* load chosen paths from the tree into the index without
* affecting the working tree nor HEAD. */
- if (pathspec) {
+ if (pathspec.nr) {
if (reset_type == MIXED)
warning(_("--mixed with paths is deprecated; use 'git reset -- <paths>' instead."));
else if (reset_type != NONE)
die_if_unmerged_cache(reset_type);
if (reset_type != SOFT) {
- struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
+ struct lock_file *lock = xcalloc(1, sizeof(*lock));
int newfd = hold_locked_index(lock, 1);
if (reset_type == MIXED) {
- if (read_from_tree(pathspec, sha1))
+ int flags = quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN;
+ if (read_from_tree(&pathspec, sha1))
return 1;
+ refresh_index(&the_index, flags, NULL, NULL,
+ _("Unstaged changes after reset:"));
} else {
int err = reset_index(sha1, reset_type, quiet);
if (reset_type == KEEP && !err)
die(_("Could not reset index file to revision '%s'."), rev);
}
- if (reset_type == MIXED) { /* Report what has not been updated. */
- int flags = quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN;
- refresh_index(&the_index, flags, NULL, NULL,
- _("Unstaged changes after reset:"));
- }
-
if (write_cache(newfd, active_cache, active_nr) ||
commit_locked_index(lock))
die(_("Could not write new index file."));
}
- if (!pathspec && !unborn) {
+ if (!pathspec.nr && !unborn) {
/* Any resets without paths update HEAD to the head being
* switched to, saving the previous head in ORIG_HEAD before. */
- update_ref_status = update_refs(rev, sha1);
+ update_ref_status = reset_refs(rev, sha1);
if (reset_type == HARD && !update_ref_status && !quiet)
print_new_head_line(lookup_commit_reference(sha1));
}
- if (!pathspec)
+ if (!pathspec.nr)
remove_branch_state();
return update_ref_status;
if (prepare_revision_walk(&revs))
die("revision walk setup failed");
if (revs.tree_objects)
- mark_edges_uninteresting(revs.commits, &revs, show_edge);
+ mark_edges_uninteresting(&revs, show_edge);
if (bisect_list) {
int reaches = reaches, all = all;
memset(&opts, 0, sizeof(opts));
opts.action = REPLAY_PICK;
git_config(git_default_config, NULL);
+ if (!strcmp(argv[1], "-"))
+ argv[1] = "@{-1}";
parse_args(argc, argv, &opts);
res = sequencer_pick_revisions(&opts);
if (res < 0)
#include "parse-options.h"
#include "string-list.h"
#include "submodule.h"
+#include "pathspec.h"
static const char * const builtin_rm_usage[] = {
N_("git rm [options] [--] <file>..."),
int cmd_rm(int argc, const char **argv, const char *prefix)
{
- int i, newfd, seen_any;
- const char **pathspec, *match;
+ int i, newfd;
+ struct pathspec pathspec;
char *seen;
+ gitmodules_config();
git_config(git_default_config, NULL);
argc = parse_options(argc, argv, prefix, builtin_rm_options,
}
}
- pathspec = get_pathspec(prefix, argv);
- refresh_index(&the_index, REFRESH_QUIET, pathspec, NULL, NULL);
+ parse_pathspec(&pathspec, 0, PATHSPEC_PREFER_CWD, prefix, argv);
+ refresh_index(&the_index, REFRESH_QUIET, &pathspec, NULL, NULL);
- for (i = 0; pathspec[i] ; i++)
- /* nothing */;
- seen = xcalloc(i, 1);
+ seen = xcalloc(pathspec.nr, 1);
for (i = 0; i < active_nr; i++) {
const struct cache_entry *ce = active_cache[i];
- if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen))
+ if (!match_pathspec_depth(&pathspec, ce->name, ce_namelen(ce), 0, seen))
continue;
ALLOC_GROW(list.entry, list.nr + 1, list.alloc);
list.entry[list.nr].name = ce->name;
- list.entry[list.nr++].is_submodule = S_ISGITLINK(ce->ce_mode);
+ list.entry[list.nr].is_submodule = S_ISGITLINK(ce->ce_mode);
+ if (list.entry[list.nr++].is_submodule &&
+ !is_staging_gitmodules_ok())
+ die (_("Please, stage your changes to .gitmodules or stash them to proceed"));
}
-
- seen_any = 0;
- for (i = 0; (match = pathspec[i]) != NULL ; i++) {
- if (!seen[i]) {
- if (!ignore_unmatch) {
- die(_("pathspec '%s' did not match any files"),
- match);
+ if (pathspec.nr) {
+ const char *original;
+ int seen_any = 0;
+ for (i = 0; i < pathspec.nr; i++) {
+ original = pathspec.items[i].original;
+ if (!seen[i]) {
+ if (!ignore_unmatch) {
+ die(_("pathspec '%s' did not match any files"),
+ original);
+ }
}
+ else {
+ seen_any = 1;
+ }
+ if (!recursive && seen[i] == MATCHED_RECURSIVELY)
+ die(_("not removing '%s' recursively without -r"),
+ *original ? original : ".");
}
- else {
- seen_any = 1;
- }
- if (!recursive && seen[i] == MATCHED_RECURSIVELY)
- die(_("not removing '%s' recursively without -r"),
- *match ? match : ".");
+
+ if (!seen_any)
+ exit(0);
}
- if (!seen_any)
- exit(0);
/*
* If not forced, the file, the index and the HEAD (if exists)
* in the middle)
*/
if (!index_only) {
- int removed = 0;
+ int removed = 0, gitmodules_modified = 0;
for (i = 0; i < list.nr; i++) {
const char *path = list.entry[i].name;
if (list.entry[i].is_submodule) {
if (is_empty_dir(path)) {
if (!rmdir(path)) {
removed = 1;
+ if (!remove_path_from_gitmodules(path))
+ gitmodules_modified = 1;
continue;
}
} else {
strbuf_addstr(&buf, path);
if (!remove_dir_recursively(&buf, 0)) {
removed = 1;
+ if (!remove_path_from_gitmodules(path))
+ gitmodules_modified = 1;
strbuf_release(&buf);
continue;
- }
+ } else if (!file_exists(path))
+ /* Submodule was removed by user */
+ if (!remove_path_from_gitmodules(path))
+ gitmodules_modified = 1;
strbuf_release(&buf);
/* Fallthrough and let remove_path() fail. */
}
if (!removed)
die_errno("git rm: '%s'", path);
}
+ if (gitmodules_modified)
+ stage_updated_gitmodules();
}
if (active_cache_changed) {
#include "sideband.h"
#include "run-command.h"
#include "remote.h"
+#include "connect.h"
#include "send-pack.h"
#include "quote.h"
#include "transport.h"
msg = "needs force";
break;
+ case REF_STATUS_REJECT_STALE:
+ res = "error";
+ msg = "stale info";
+ break;
+
case REF_STATUS_REJECT_ALREADY_EXISTS:
res = "error";
msg = "already exists";
int flags;
unsigned int reject_reasons;
int progress = -1;
+ struct push_cas_option cas = {0};
argv++;
for (i = 1; i < argc; i++, argv++) {
helper_status = 1;
continue;
}
+ if (!strcmp(arg, "--" CAS_OPT_NAME)) {
+ if (parse_push_cas_option(&cas, NULL, 0) < 0)
+ exit(1);
+ continue;
+ }
+ if (!strcmp(arg, "--no-" CAS_OPT_NAME)) {
+ if (parse_push_cas_option(&cas, NULL, 1) < 0)
+ exit(1);
+ continue;
+ }
+ if (!prefixcmp(arg, "--" CAS_OPT_NAME "=")) {
+ if (parse_push_cas_option(&cas,
+ strchr(arg, '=') + 1, 0) < 0)
+ exit(1);
+ continue;
+ }
usage(send_pack_usage);
}
if (!dest) {
if (match_push_refs(local_refs, &remote_refs, nr_refspecs, refspecs, flags))
return -1;
+ if (!is_empty_cas(&cas))
+ apply_push_cas(&cas, remote, remote_refs);
+
set_ref_status_for_push(remote_refs, args.send_mirror,
args.force_update);
if (argc == 2) {
if (!strcmp(argv[1], "-s") ||
- !strcmp(argv[1], "--strip-comments")) {
- strip_comments = 1;
+ !strcmp(argv[1], "--strip-comments")) {
+ strip_comments = 1;
} else if (!strcmp(argv[1], "-c") ||
- !strcmp(argv[1], "--comment-lines")) {
- mode = COMMENT_LINES;
+ !strcmp(argv[1], "--comment-lines")) {
+ mode = COMMENT_LINES;
} else {
mode = INVAL;
}
if (annotate)
create_tag(object, tag, &buf, &opt, prev, object);
- lock = lock_any_ref_for_update(ref.buf, prev, 0);
+ lock = lock_any_ref_for_update(ref.buf, prev, 0, NULL);
if (!lock)
die(_("%s: cannot lock the ref"), ref.buf);
if (write_ref_sha1(lock, object, NULL) < 0)
#include "refs.h"
#include "resolve-undo.h"
#include "parse-options.h"
+#include "pathspec.h"
/*
* Default to not allowing changes to the list of files. The
*/
int pos;
int has_head = 1;
- const char **paths = get_pathspec(prefix, av + 1);
struct pathspec pathspec;
- init_pathspec(&pathspec, paths);
+ parse_pathspec(&pathspec, 0,
+ PATHSPEC_PREFER_CWD,
+ prefix, av + 1);
if (read_ref("HEAD", head_sha1))
/* If there is no HEAD, that means it is an initial
#include "refs.h"
#include "builtin.h"
#include "parse-options.h"
+#include "quote.h"
+#include "argv-array.h"
static const char * const git_update_ref_usage[] = {
N_("git update-ref [options] -d <refname> [<oldval>]"),
N_("git update-ref [options] <refname> <newval> [<oldval>]"),
+ N_("git update-ref [options] --stdin [-z]"),
NULL
};
+static int updates_alloc;
+static int updates_count;
+static const struct ref_update **updates;
+
+static char line_termination = '\n';
+static int update_flags;
+
+static struct ref_update *update_alloc(void)
+{
+ struct ref_update *update;
+
+ /* Allocate and zero-init a struct ref_update */
+ update = xcalloc(1, sizeof(*update));
+ ALLOC_GROW(updates, updates_count + 1, updates_alloc);
+ updates[updates_count++] = update;
+
+ /* Store and reset accumulated options */
+ update->flags = update_flags;
+ update_flags = 0;
+
+ return update;
+}
+
+static void update_store_ref_name(struct ref_update *update,
+ const char *ref_name)
+{
+ if (check_refname_format(ref_name, REFNAME_ALLOW_ONELEVEL))
+ die("invalid ref format: %s", ref_name);
+ update->ref_name = xstrdup(ref_name);
+}
+
+static void update_store_new_sha1(struct ref_update *update,
+ const char *newvalue)
+{
+ if (*newvalue && get_sha1(newvalue, update->new_sha1))
+ die("invalid new value for ref %s: %s",
+ update->ref_name, newvalue);
+}
+
+static void update_store_old_sha1(struct ref_update *update,
+ const char *oldvalue)
+{
+ if (*oldvalue && get_sha1(oldvalue, update->old_sha1))
+ die("invalid old value for ref %s: %s",
+ update->ref_name, oldvalue);
+
+ /* We have an old value if non-empty, or if empty without -z */
+ update->have_old = *oldvalue || line_termination;
+}
+
+static const char *parse_arg(const char *next, struct strbuf *arg)
+{
+ /* Parse SP-terminated, possibly C-quoted argument */
+ if (*next != '"')
+ while (*next && !isspace(*next))
+ strbuf_addch(arg, *next++);
+ else if (unquote_c_style(arg, next, &next))
+ die("badly quoted argument: %s", next);
+
+ /* Return position after the argument */
+ return next;
+}
+
+static const char *parse_first_arg(const char *next, struct strbuf *arg)
+{
+ /* Parse argument immediately after "command SP" */
+ strbuf_reset(arg);
+ if (line_termination) {
+ /* Without -z, use the next argument */
+ next = parse_arg(next, arg);
+ } else {
+ /* With -z, use rest of first NUL-terminated line */
+ strbuf_addstr(arg, next);
+ next = next + arg->len;
+ }
+ return next;
+}
+
+static const char *parse_next_arg(const char *next, struct strbuf *arg)
+{
+ /* Parse next SP-terminated or NUL-terminated argument, if any */
+ strbuf_reset(arg);
+ if (line_termination) {
+ /* Without -z, consume SP and use next argument */
+ if (!*next)
+ return NULL;
+ if (*next != ' ')
+ die("expected SP but got: %s", next);
+ next = parse_arg(next + 1, arg);
+ } else {
+ /* With -z, read the next NUL-terminated line */
+ if (*next)
+ die("expected NUL but got: %s", next);
+ if (strbuf_getline(arg, stdin, '\0') == EOF)
+ return NULL;
+ next = arg->buf + arg->len;
+ }
+ return next;
+}
+
+static void parse_cmd_update(const char *next)
+{
+ struct strbuf ref = STRBUF_INIT;
+ struct strbuf newvalue = STRBUF_INIT;
+ struct strbuf oldvalue = STRBUF_INIT;
+ struct ref_update *update;
+
+ update = update_alloc();
+
+ if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0])
+ update_store_ref_name(update, ref.buf);
+ else
+ die("update line missing <ref>");
+
+ if ((next = parse_next_arg(next, &newvalue)) != NULL)
+ update_store_new_sha1(update, newvalue.buf);
+ else
+ die("update %s missing <newvalue>", ref.buf);
+
+ if ((next = parse_next_arg(next, &oldvalue)) != NULL)
+ update_store_old_sha1(update, oldvalue.buf);
+ else if(!line_termination)
+ die("update %s missing [<oldvalue>] NUL", ref.buf);
+
+ if (next && *next)
+ die("update %s has extra input: %s", ref.buf, next);
+}
+
+static void parse_cmd_create(const char *next)
+{
+ struct strbuf ref = STRBUF_INIT;
+ struct strbuf newvalue = STRBUF_INIT;
+ struct ref_update *update;
+
+ update = update_alloc();
+
+ if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0])
+ update_store_ref_name(update, ref.buf);
+ else
+ die("create line missing <ref>");
+
+ if ((next = parse_next_arg(next, &newvalue)) != NULL)
+ update_store_new_sha1(update, newvalue.buf);
+ else
+ die("create %s missing <newvalue>", ref.buf);
+ if (is_null_sha1(update->new_sha1))
+ die("create %s given zero new value", ref.buf);
+
+ if (next && *next)
+ die("create %s has extra input: %s", ref.buf, next);
+}
+
+static void parse_cmd_delete(const char *next)
+{
+ struct strbuf ref = STRBUF_INIT;
+ struct strbuf oldvalue = STRBUF_INIT;
+ struct ref_update *update;
+
+ update = update_alloc();
+
+ if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0])
+ update_store_ref_name(update, ref.buf);
+ else
+ die("delete line missing <ref>");
+
+ if ((next = parse_next_arg(next, &oldvalue)) != NULL)
+ update_store_old_sha1(update, oldvalue.buf);
+ else if(!line_termination)
+ die("delete %s missing [<oldvalue>] NUL", ref.buf);
+ if (update->have_old && is_null_sha1(update->old_sha1))
+ die("delete %s given zero old value", ref.buf);
+
+ if (next && *next)
+ die("delete %s has extra input: %s", ref.buf, next);
+}
+
+static void parse_cmd_verify(const char *next)
+{
+ struct strbuf ref = STRBUF_INIT;
+ struct strbuf value = STRBUF_INIT;
+ struct ref_update *update;
+
+ update = update_alloc();
+
+ if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0])
+ update_store_ref_name(update, ref.buf);
+ else
+ die("verify line missing <ref>");
+
+ if ((next = parse_next_arg(next, &value)) != NULL) {
+ update_store_old_sha1(update, value.buf);
+ update_store_new_sha1(update, value.buf);
+ } else if(!line_termination)
+ die("verify %s missing [<oldvalue>] NUL", ref.buf);
+
+ if (next && *next)
+ die("verify %s has extra input: %s", ref.buf, next);
+}
+
+static void parse_cmd_option(const char *next)
+{
+ if (!strcmp(next, "no-deref"))
+ update_flags |= REF_NODEREF;
+ else
+ die("option unknown: %s", next);
+}
+
+static void update_refs_stdin(void)
+{
+ struct strbuf cmd = STRBUF_INIT;
+
+ /* Read each line dispatch its command */
+ while (strbuf_getline(&cmd, stdin, line_termination) != EOF)
+ if (!cmd.buf[0])
+ die("empty command in input");
+ else if (isspace(*cmd.buf))
+ die("whitespace before command: %s", cmd.buf);
+ else if (!prefixcmp(cmd.buf, "update "))
+ parse_cmd_update(cmd.buf + 7);
+ else if (!prefixcmp(cmd.buf, "create "))
+ parse_cmd_create(cmd.buf + 7);
+ else if (!prefixcmp(cmd.buf, "delete "))
+ parse_cmd_delete(cmd.buf + 7);
+ else if (!prefixcmp(cmd.buf, "verify "))
+ parse_cmd_verify(cmd.buf + 7);
+ else if (!prefixcmp(cmd.buf, "option "))
+ parse_cmd_option(cmd.buf + 7);
+ else
+ die("unknown command: %s", cmd.buf);
+
+ strbuf_release(&cmd);
+}
+
int cmd_update_ref(int argc, const char **argv, const char *prefix)
{
const char *refname, *oldval, *msg = NULL;
unsigned char sha1[20], oldsha1[20];
- int delete = 0, no_deref = 0, flags = 0;
+ int delete = 0, no_deref = 0, read_stdin = 0, end_null = 0, flags = 0;
struct option options[] = {
OPT_STRING( 'm', NULL, &msg, N_("reason"), N_("reason of the update")),
OPT_BOOL('d', NULL, &delete, N_("delete the reference")),
OPT_BOOL( 0 , "no-deref", &no_deref,
N_("update <refname> not the one it points to")),
+ OPT_BOOL('z', NULL, &end_null, N_("stdin has NUL-terminated arguments")),
+ OPT_BOOL( 0 , "stdin", &read_stdin, N_("read updates from stdin")),
OPT_END(),
};
if (msg && !*msg)
die("Refusing to perform update with empty message.");
+ if (read_stdin) {
+ if (delete || no_deref || argc > 0)
+ usage_with_options(git_update_ref_usage, options);
+ if (end_null)
+ line_termination = '\0';
+ update_refs_stdin();
+ return update_refs(msg, updates, updates_count, DIE_ON_ERR);
+ }
+
+ if (end_null)
+ usage_with_options(git_update_ref_usage, options);
+
if (delete) {
if (argc < 1 || argc > 2)
usage_with_options(git_update_ref_usage, options);
if (size && !s.avail_in) {
ssize_t rsize = size < sizeof(ibuf) ? size : sizeof(ibuf);
- if (xread(fd, ibuf, rsize) != rsize)
+ if (read_in_full(fd, ibuf, rsize) != rsize)
die("failed to read %d bytes from '%s'",
(int)rsize, path);
offset += rsize;
#define CACHE_SIGNATURE 0x44495243 /* "DIRC" */
struct cache_header {
- unsigned int hdr_signature;
- unsigned int hdr_version;
- unsigned int hdr_entries;
+ uint32_t hdr_signature;
+ uint32_t hdr_version;
+ uint32_t hdr_entries;
};
#define INDEX_FORMAT_LB 2
* check it for equality in the 32 bits we save.
*/
struct cache_time {
- unsigned int sec;
- unsigned int nsec;
+ uint32_t sec;
+ uint32_t nsec;
};
struct stat_data {
#error "CE_EXTENDED_FLAGS out of range"
#endif
+struct pathspec;
+
/*
* Copy the sha1 and stat state of a cache entry from one to
* another. But we never change the name, or the hash state!
#define GIT_NOTES_REWRITE_REF_ENVIRONMENT "GIT_NOTES_REWRITE_REF"
#define GIT_NOTES_REWRITE_MODE_ENVIRONMENT "GIT_NOTES_REWRITE_MODE"
#define GIT_LITERAL_PATHSPECS_ENVIRONMENT "GIT_LITERAL_PATHSPECS"
+#define GIT_GLOB_PATHSPECS_ENVIRONMENT "GIT_GLOB_PATHSPECS"
+#define GIT_NOGLOB_PATHSPECS_ENVIRONMENT "GIT_NOGLOB_PATHSPECS"
+#define GIT_ICASE_PATHSPECS_ENVIRONMENT "GIT_ICASE_PATHSPECS"
/*
* This environment variable is expected to contain a boolean indicating
extern const char *setup_git_directory_gently(int *);
extern const char *setup_git_directory(void);
extern char *prefix_path(const char *prefix, int len, const char *path);
+extern char *prefix_path_gently(const char *prefix, int len, int *remaining, const char *path);
extern const char *prefix_filename(const char *prefix, int len, const char *path);
extern int check_filename(const char *prefix, const char *name);
extern void verify_filename(const char *prefix,
/* Initialize and use the cache information */
extern int read_index(struct index_state *);
-extern int read_index_preload(struct index_state *, const char **pathspec);
+extern int read_index_preload(struct index_state *, const struct pathspec *pathspec);
extern int read_index_from(struct index_state *, const char *path);
extern int is_index_unborn(struct index_state *);
extern int read_index_unmerged(struct index_state *);
extern int ie_match_stat(const struct index_state *, const struct cache_entry *, struct stat *, unsigned int);
extern int ie_modified(const struct index_state *, const struct cache_entry *, struct stat *, unsigned int);
-#define PATHSPEC_ONESTAR 1 /* the pathspec pattern satisfies GFNM_ONESTAR */
-
-struct pathspec {
- const char **raw; /* get_pathspec() result, not freed by free_pathspec() */
- int nr;
- unsigned int has_wildcard:1;
- unsigned int recursive:1;
- int max_depth;
- struct pathspec_item {
- const char *match;
- int len;
- int nowildcard_len;
- int flags;
- } *items;
-};
-
-extern int init_pathspec(struct pathspec *, const char **);
-extern void free_pathspec(struct pathspec *);
extern int ce_path_match(const struct cache_entry *ce, const struct pathspec *pathspec);
-extern int limit_pathspec_to_literal(void);
-
#define HASH_WRITE_OBJECT 1
#define HASH_FORMAT_CHECK 2
extern int index_fd(unsigned char *sha1, int fd, struct stat *st, enum object_type type, const char *path, unsigned flags);
#define REFRESH_IGNORE_MISSING 0x0008 /* ignore non-existent */
#define REFRESH_IGNORE_SUBMODULES 0x0010 /* ignore submodules */
#define REFRESH_IN_PORCELAIN 0x0020 /* user friendly output, not "needs update" */
-extern int refresh_index(struct index_state *, unsigned int flags, const char **pathspec, char *seen, const char *header_msg);
+extern int refresh_index(struct index_state *, unsigned int flags, const struct pathspec *pathspec, char *seen, const char *header_msg);
struct lock_file {
struct lock_file *next;
const char *real_path_if_valid(const char *path);
const char *absolute_path(const char *path);
const char *relative_path(const char *in, const char *prefix, struct strbuf *sb);
+int normalize_path_copy_len(char *dst, const char *src, int *prefix_len);
int normalize_path_copy(char *dst, const char *src);
int longest_ancestor_length(const char *path, struct string_list *prefixes);
char *strip_path_suffix(const char *path, const char *suffix);
struct packed_git *p;
};
-struct ref {
- struct ref *next;
- unsigned char old_sha1[20];
- unsigned char new_sha1[20];
- char *symref;
- unsigned int
- force:1,
- forced_update:1,
- deletion:1,
- matched:1;
-
- /*
- * Order is important here, as we write to FETCH_HEAD
- * in numeric order. And the default NOT_FOR_MERGE
- * should be 0, so that xcalloc'd structures get it
- * by default.
- */
- enum {
- FETCH_HEAD_MERGE = -1,
- FETCH_HEAD_NOT_FOR_MERGE = 0,
- FETCH_HEAD_IGNORE = 1
- } fetch_head_status;
-
- enum {
- REF_STATUS_NONE = 0,
- REF_STATUS_OK,
- REF_STATUS_REJECT_NONFASTFORWARD,
- REF_STATUS_REJECT_ALREADY_EXISTS,
- REF_STATUS_REJECT_NODELETE,
- REF_STATUS_REJECT_FETCH_FIRST,
- REF_STATUS_REJECT_NEEDS_FORCE,
- REF_STATUS_UPTODATE,
- REF_STATUS_REMOTE_REJECT,
- REF_STATUS_EXPECTING_REPORT
- } status;
- char *remote_status;
- struct ref *peer_ref; /* when renaming */
- char name[FLEX_ARRAY]; /* more */
-};
-
-#define REF_NORMAL (1u << 0)
-#define REF_HEADS (1u << 1)
-#define REF_TAGS (1u << 2)
-
-extern struct ref *find_ref_by_name(const struct ref *list, const char *name);
-
-#define CONNECT_VERBOSE (1u << 0)
-extern struct child_process *git_connect(int fd[2], const char *url, const char *prog, int flags);
-extern int finish_connect(struct child_process *conn);
-extern int git_connection_is_socket(struct child_process *conn);
-struct extra_have_objects {
- int nr, alloc;
- unsigned char (*array)[20];
-};
-extern struct ref **get_remote_heads(int in, char *src_buf, size_t src_len,
- struct ref **list, unsigned int flags,
- struct extra_have_objects *);
-extern int server_supports(const char *feature);
-extern int parse_feature_request(const char *features, const char *feature);
-extern const char *server_feature_value(const char *feature, int *len_ret);
-extern const char *parse_feature_value(const char *feature_list, const char *feature, int *len_ret);
-
extern struct packed_git *parse_pack_index(unsigned char *sha1, const char *idx_path);
/* A hook for count-objects to report invalid files in pack directory */
extern int git_config_early(config_fn_t fn, void *, const char *repo_config);
extern int git_parse_ulong(const char *, unsigned long *);
extern int git_config_int(const char *, const char *);
+extern int64_t git_config_int64(const char *, const char *);
extern unsigned long git_config_ulong(const char *, const char *);
extern int git_config_bool_or_int(const char *, const char *, int *);
extern int git_config_bool(const char *, const char *);
* return 0 if success, 1 - if addition of a file failed and
* ADD_FILES_IGNORE_ERRORS was specified in flags
*/
-int add_files_to_cache(const char *prefix, const char **pathspec, int flags);
+int add_files_to_cache(const char *prefix, const struct pathspec *pathspec, int flags);
/* diff.c */
extern int diff_auto_refresh_index;
#define ws_tab_width(rule) ((rule) & WS_TAB_WIDTH_MASK)
/* ls-files */
-int report_path_error(const char *ps_matched, const char **pathspec, const char *prefix);
+int report_path_error(const char *ps_matched, const struct pathspec *pathspec, const char *prefix);
void overlay_tree_on_cache(const char *tree_name, const char *prefix);
char *alias_lookup(const char *alias);
#include "refs.h"
#include "userdiff.h"
#include "sha1-array.h"
+#include "revision.h"
static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr, int n, int num_parent)
{
int i, num_paths, needsep, show_log_first, num_parent = parents->nr;
diffopts = *opt;
- diff_tree_setup_paths(diffopts.pathspec.raw, &diffopts);
+ copy_pathspec(&diffopts.pathspec, &opt->pathspec);
diffopts.output_format = DIFF_FORMAT_NO_OUTPUT;
DIFF_OPT_SET(&diffopts, RECURSIVE);
DIFF_OPT_CLR(&diffopts, ALLOW_EXTERNAL);
free(tmp);
}
- diff_tree_release_paths(&diffopts);
+ free_pathspec(&diffopts.pathspec);
}
void diff_tree_combined_merge(const struct commit *commit, int dense,
struct rev_info *rev)
{
- struct commit_list *parent = commit->parents;
+ struct commit_list *parent = get_saved_parents(rev, commit);
struct sha1_array parents = SHA1_ARRAY_INIT;
while (parent) {
return c;
}
+struct commit_list *copy_commit_list(struct commit_list *list)
+{
+ struct commit_list *head = NULL;
+ struct commit_list **pp = &head;
+ while (list) {
+ struct commit_list *new;
+ new = xmalloc(sizeof(struct commit_list));
+ new->item = list->item;
+ new->next = NULL;
+ *pp = new;
+ pp = &new->next;
+ list = list->next;
+ }
+ return head;
+}
+
void free_commit_list(struct commit_list *list)
{
while (list) {
struct commit_list **list);
void commit_list_sort_by_date(struct commit_list **list);
+/* Shallow copy of the input list */
+struct commit_list *copy_commit_list(struct commit_list *list);
+
void free_commit_list(struct commit_list *list);
/* Commit formats */
int depth, int shallow_flag, int not_shallow_flag);
extern void check_shallow_file_for_update(void);
extern void set_alternate_shallow_file(const char *path);
+extern int write_shallow_commits(struct strbuf *out, int use_pack_protocol);
+extern void setup_alternate_shallow(struct lock_file *shallow_lock,
+ const char **alternate_shallow_file);
+extern char *setup_temporary_shallow(void);
int is_descendant_of(struct commit *, struct commit_list *);
int in_merge_bases(struct commit *, struct commit *);
extern int interactive_add(int argc, const char **argv, const char *prefix, int patch);
extern int run_add_interactive(const char *revision, const char *patch_mode,
- const char **pathspec);
+ const struct pathspec *pathspec);
static inline int single_parent(struct commit *commit)
{
+++ /dev/null
-#include "../git-compat-util.h"
-#undef write
-
-/*
- * Version of write that will write at most INT_MAX bytes.
- * Workaround a xnu bug on Mac OS X
- */
-ssize_t clipped_write(int fildes, const void *buf, size_t nbyte)
-{
- if (nbyte > INT_MAX)
- nbyte = INT_MAX;
- return write(fildes, buf, nbyte);
-}
#define find_last_dir_sep mingw_find_last_dir_sep
#define PATH_SEP ';'
#define PRIuMAX "I64u"
+#define PRId64 "I64d"
void mingw_open_html(const char *path);
#define open_html mingw_open_html
if (output_fd >= 0) {
close(output_fd);
strcpy(path + len, auml_nfd);
- /* Indicate to the user, that we can configure it to true */
- if (!access(path, R_OK))
- git_config_set("core.precomposeunicode", "false");
- /* To be backward compatible, set precomposed_unicode to 0 */
- precomposed_unicode = 0;
+ precomposed_unicode = access(path, R_OK) ? 0 : 1;
+ git_config_set("core.precomposeunicode", precomposed_unicode ? "true" : "false");
strcpy(path + len, auml_nfc);
if (unlink(path))
die_errno(_("failed to unlink '%s'"), path);
return 0;
}
-static int git_parse_long(const char *value, long *ret)
+static int git_parse_signed(const char *value, intmax_t *ret, intmax_t max)
{
if (value && *value) {
char *end;
val = strtoimax(value, &end, 0);
if (errno == ERANGE)
return 0;
- if (!parse_unit_factor(end, &factor))
+ if (!parse_unit_factor(end, &factor)) {
+ errno = EINVAL;
return 0;
+ }
uval = abs(val);
uval *= factor;
- if ((uval > maximum_signed_value_of_type(long)) ||
- (abs(val) > uval))
+ if (uval > max || abs(val) > uval) {
+ errno = ERANGE;
return 0;
+ }
val *= factor;
*ret = val;
return 1;
}
+ errno = EINVAL;
return 0;
}
-int git_parse_ulong(const char *value, unsigned long *ret)
+int git_parse_unsigned(const char *value, uintmax_t *ret, uintmax_t max)
{
if (value && *value) {
char *end;
if (errno == ERANGE)
return 0;
oldval = val;
- if (!parse_unit_factor(end, &val))
+ if (!parse_unit_factor(end, &val)) {
+ errno = EINVAL;
return 0;
- if ((val > maximum_unsigned_value_of_type(long)) ||
- (oldval > val))
+ }
+ if (val > max || oldval > val) {
+ errno = ERANGE;
return 0;
+ }
*ret = val;
return 1;
}
+ errno = EINVAL;
return 0;
}
-static void die_bad_config(const char *name)
+static int git_parse_int(const char *value, int *ret)
{
+ intmax_t tmp;
+ if (!git_parse_signed(value, &tmp, maximum_signed_value_of_type(int)))
+ return 0;
+ *ret = tmp;
+ return 1;
+}
+
+static int git_parse_int64(const char *value, int64_t *ret)
+{
+ intmax_t tmp;
+ if (!git_parse_signed(value, &tmp, maximum_signed_value_of_type(int64_t)))
+ return 0;
+ *ret = tmp;
+ return 1;
+}
+
+int git_parse_ulong(const char *value, unsigned long *ret)
+{
+ uintmax_t tmp;
+ if (!git_parse_unsigned(value, &tmp, maximum_unsigned_value_of_type(long)))
+ return 0;
+ *ret = tmp;
+ return 1;
+}
+
+static void die_bad_number(const char *name, const char *value)
+{
+ const char *reason = errno == ERANGE ?
+ "out of range" :
+ "invalid unit";
+ if (!value)
+ value = "";
+
if (cf && cf->name)
- die("bad config value for '%s' in %s", name, cf->name);
- die("bad config value for '%s'", name);
+ die("bad numeric config value '%s' for '%s' in %s: %s",
+ value, name, cf->name, reason);
+ die("bad numeric config value '%s' for '%s': %s", value, name, reason);
}
int git_config_int(const char *name, const char *value)
{
- long ret = 0;
- if (!git_parse_long(value, &ret))
- die_bad_config(name);
+ int ret;
+ if (!git_parse_int(value, &ret))
+ die_bad_number(name, value);
+ return ret;
+}
+
+int64_t git_config_int64(const char *name, const char *value)
+{
+ int64_t ret;
+ if (!git_parse_int64(value, &ret))
+ die_bad_number(name, value);
return ret;
}
{
unsigned long ret;
if (!git_parse_ulong(value, &ret))
- die_bad_config(name);
+ die_bad_number(name, value);
return ret;
}
int git_config_maybe_bool(const char *name, const char *value)
{
- long v = git_config_maybe_bool_text(name, value);
+ int v = git_config_maybe_bool_text(name, value);
if (0 <= v)
return v;
- if (git_parse_long(value, &v))
+ if (git_parse_int(value, &v))
return !!v;
return -1;
}
NO_MEMMEM = YesPlease
USE_ST_TIMESPEC = YesPlease
HAVE_DEV_TTY = YesPlease
- NEEDS_CLIPPED_WRITE = YesPlease
COMPAT_OBJS += compat/precompose_utf8.o
BASIC_CFLAGS += -DPRECOMPOSE_UNICODE
endif
#include "refs.h"
#include "run-command.h"
#include "remote.h"
+#include "connect.h"
#include "url.h"
static char *server_capabilities;
--- /dev/null
+#ifndef CONNECT_H
+#define CONNECT_H
+
+#define CONNECT_VERBOSE (1u << 0)
+extern struct child_process *git_connect(int fd[2], const char *url, const char *prog, int flags);
+extern int finish_connect(struct child_process *conn);
+extern int git_connection_is_socket(struct child_process *conn);
+extern int server_supports(const char *feature);
+extern int parse_feature_request(const char *features, const char *feature);
+extern const char *server_feature_value(const char *feature, int *len_ret);
+extern const char *parse_feature_value(const char *feature_list, const char *feature, int *len_ret);
+
+#endif
}
sub get_blame {
- my ($commits, $source, $start, $len, $from) = @_;
- $len = 1 unless defined($len);
- return if $len == 0;
+ my ($commits, $source, $from, $ranges) = @_;
+ return unless @$ranges;
open my $f, '-|',
- qw(git blame --porcelain -C), '-L', "$start,+$len",
+ qw(git blame --porcelain -C),
+ map({"-L$_->[0],+$_->[1]"} @$ranges),
'--since', $since, "$from^", '--', $source or die;
while (<$f>) {
if (/^([0-9a-f]{40}) \d+ \d+ \d+$/) {
close $f;
}
+sub blame_sources {
+ my ($sources, $commits) = @_;
+ for my $s (keys %$sources) {
+ for my $id (keys %{$sources->{$s}}) {
+ get_blame($commits, $s, $id, $sources->{$s}{$id});
+ }
+ }
+}
+
sub scan_patches {
- my ($commits, $id, $f) = @_;
+ my ($sources, $id, $f) = @_;
my $source;
while (<$f>) {
if (/^From ([0-9a-f]{40}) Mon Sep 17 00:00:00 2001$/) {
} elsif (/^--- /) {
die "Cannot parse hunk source: $_\n";
} elsif (/^@@ -(\d+)(?:,(\d+))?/ && $source) {
- get_blame($commits, $source, $1, $2, $id);
+ my $len = defined($2) ? $2 : 1;
+ push @{$sources->{$source}{$id}}, [$1, $len] if $len;
}
}
}
}
}
-my %commits;
+my %sources;
for (@files) {
- scan_patch_file(\%commits, $_);
+ scan_patch_file(\%sources, $_);
}
if (@rev_args) {
- scan_rev_args(\%commits, \@rev_args)
+ scan_rev_args(\%sources, \@rev_args)
}
+
+my %commits;
+blame_sources(\%sources, \%commits);
import_commits(\%commits);
my $contacts = {};
# This could be traditional "merge <msg> HEAD <commit>..." and the
# way we can tell it is to see if the second token is HEAD, but some
-# people might have misused the interface and used a committish that
+# people might have misused the interface and used a commit-ish that
# is the same as HEAD there instead. Traditional format never would
# have "-m" so it is an additional safety measure to check for it.
cat <<-EOF
To: $recipients
Subject: ${emailprefix}$projectdesc $refname_type $short_refname ${change_type}d. $describe
+ MIME-Version: 1.0
+ Content-Type: text/plain; charset=utf-8
+ Content-Transfer-Encoding: 8bit
X-Git-Refname: $refname
X-Git-Reftype: $refname_type
X-Git-Oldrev: $oldrev
echo " was $oldrev"
echo ""
echo $LOGBEGIN
- git show -s --pretty=oneline $oldrev
+ git diff-tree -s --always --encoding=UTF-8 --pretty=oneline $oldrev
echo $LOGEND
}
# performed on them
if [ -n "$prevtag" ]; then
# Show changes since the previous release
- git rev-list --pretty=short "$prevtag..$newrev" | git shortlog
+ git shortlog "$prevtag..$newrev"
else
# No previous tag, show all the changes since time
# began
- git rev-list --pretty=short $newrev | git shortlog
+ git shortlog $newrev
fi
;;
*)
echo " was $oldrev"
echo ""
echo $LOGBEGIN
- git show -s --pretty=oneline $oldrev
+ git diff-tree -s --always --encoding=UTF-8 --pretty=oneline $oldrev
echo $LOGEND
}
echo ""
if [ "$newrev_type" = "commit" ]; then
echo $LOGBEGIN
- git show --no-color --root -s --pretty=medium $newrev
+ git diff-tree -s --always --encoding=UTF-8 --pretty=medium $newrev
echo $LOGEND
else
# What can we do here? The tag marks an object that is not
echo " was $oldrev"
echo ""
echo $LOGBEGIN
- git show -s --pretty=oneline $oldrev
+ git diff-tree -s --always --encoding=UTF-8 --pretty=oneline $oldrev
echo $LOGEND
}
all: build
+test: all
+ $(MAKE) -C t
+
+check: perlcritic test
+
install_pm:
install $(GIT_MEDIAWIKI_PM) $(INSTLIBDIR)/$(GIT_MEDIAWIKI_PM)
rm $(INSTLIBDIR)/$(GIT_MEDIAWIKI_PM)
perlcritic:
- perlcritic -2 *.perl
+ perlcritic -5 $(SCRIPT_PERL)
+ -perlcritic -2 $(SCRIPT_PERL)
+
+.PHONY: all test check install_pm install clean perlcritic
print {*STDOUT} "import\n";
print {*STDOUT} "list\n";
print {*STDOUT} "push\n";
+ if ($dumb_push) {
+ print {*STDOUT} "no-private-update\n";
+ }
print {*STDOUT} "\n";
return;
}
}
if (!$dumb_push) {
run_git(qq(notes --ref=${remotename}/mediawiki add -f -m "mediawiki_revision: ${mw_revision}" ${sha1_commit}));
- run_git(qq(update-ref -m "Git-MediaWiki push" refs/mediawiki/${remotename}/master ${sha1_commit} ${sha1_child}));
}
}
# or
# % git clone bzr::lp:myrepo
#
-# If you want to specify which branches you want track (per repo):
-# git config remote-bzr.branches 'trunk, devel, test'
+# If you want to specify which branches you want to track (per repo):
+# % git config remote.origin.bzr-branches 'trunk, devel, test'
+#
+# Where 'origin' is the name of the repository you want to specify the
+# branches.
#
import sys
if not m:
return None
_, name, email, date, tz = m.groups()
+ name = name.decode('utf-8')
committer = '%s <%s>' % (name, email)
tz = int(tz)
tz = ((tz / 100) * 3600) + ((tz % 100) * 60)
return (committer, int(date), tz)
def rev_to_mark(rev):
- global marks
return marks.from_rev(rev)
def mark_to_rev(mark):
- global marks
return marks.to_rev(mark)
def fixup_user(user):
return modified, removed
def export_files(tree, files):
- global marks, filenodes
-
final = []
for path, fid in files.iteritems():
kind = tree.kind(fid)
return final
def export_branch(repo, name):
- global prefix
-
ref = '%s/heads/%s' % (prefix, name)
tip = marks.get_tip(name)
marks.set_tip(name, revid)
def export_tag(repo, name):
- global tags, prefix
-
ref = '%s/tags/%s' % (prefix, name)
print "reset %s" % ref
print "from :%u" % rev_to_mark(tags[name])
print
def do_import(parser):
- global dirname
-
repo = parser.repo
path = os.path.join(dirname, 'marks-git')
sys.stdout.flush()
def parse_blob(parser):
- global blob_marks
-
parser.next()
mark = parser.get_mark()
parser.next()
class CustomTree():
def __init__(self, branch, revid, parents, files):
- global files_cache
-
self.updates = {}
self.branch = branch
add_entry(fid, dirname, 'directory')
return fid
- def add_entry(fid, path, kind, mode = None):
+ def add_entry(fid, path, kind, mode=None):
dirname, basename = os.path.split(path)
parent_fid = get_parent(dirname, basename)
self.files[path] = [change[0], None]
changes.append(change)
- def update_entry(fid, path, kind, mode = None):
+ def update_entry(fid, path, kind, mode=None):
dirname, basename = os.path.split(path)
parent_fid = get_parent(dirname, basename)
return string
def parse_commit(parser):
- global marks, blob_marks, parsed_refs
- global mode
-
parents = []
ref = parser[1]
marks.new_mark(revid, commit_mark)
def parse_reset(parser):
- global parsed_refs
-
ref = parser[1]
parser.next()
parsed_refs[ref] = mark_to_rev(from_mark)
def do_export(parser):
- global parsed_refs, dirname
-
parser.next()
for line in parser.each_block('done'):
branch.generate_revision_history(revid, marks.get_tip(name))
if name in peers:
- peer = bzrlib.branch.Branch.open(peers[name])
+ peer = bzrlib.branch.Branch.open(peers[name],
+ possible_transports=transports)
try:
peer.bzrdir.push_branch(branch, revision_id=revid)
except bzrlib.errors.DivergedBranches:
print
def do_capabilities(parser):
- global dirname
-
print "import"
print "export"
print "refspec refs/heads/*:%s/heads/*" % prefix
return not True in [c in name for c in '~^: \\']
def do_list(parser):
- global tags
-
master_branch = None
for name in branches:
def clone(path, remote_branch):
try:
- bdir = bzrlib.bzrdir.BzrDir.create(path)
+ bdir = bzrlib.bzrdir.BzrDir.create(path, possible_transports=transports)
except bzrlib.errors.AlreadyControlDirError:
- bdir = bzrlib.bzrdir.BzrDir.open(path)
+ bdir = bzrlib.bzrdir.BzrDir.open(path, possible_transports=transports)
repo = bdir.find_repository()
repo.fetch(remote_branch.repository)
return remote_branch.sprout(bdir, repository=repo)
def get_remote_branch(name):
- global dirname, branches
-
- remote_branch = bzrlib.branch.Branch.open(branches[name])
+ remote_branch = bzrlib.branch.Branch.open(branches[name],
+ possible_transports=transports)
if isinstance(remote_branch.user_transport, bzrlib.transport.local.LocalTransport):
return remote_branch
branch_path = os.path.join(dirname, 'clone', name)
try:
- branch = bzrlib.branch.Branch.open(branch_path)
+ branch = bzrlib.branch.Branch.open(branch_path,
+ possible_transports=transports)
except bzrlib.errors.NotBranchError:
# clone
branch = clone(branch_path, remote_branch)
yield name, branch.base
def get_repo(url, alias):
- global dirname, peer, branches
-
normal_url = bzrlib.urlutils.normalize_url(url)
- origin = bzrlib.bzrdir.BzrDir.open(url)
+ origin = bzrlib.bzrdir.BzrDir.open(url, possible_transports=transports)
is_local = isinstance(origin.transport, bzrlib.transport.local.LocalTransport)
shared_path = os.path.join(gitdir, 'bzr')
try:
- shared_dir = bzrlib.bzrdir.BzrDir.open(shared_path)
+ shared_dir = bzrlib.bzrdir.BzrDir.open(shared_path,
+ possible_transports=transports)
except bzrlib.errors.NotBranchError:
- shared_dir = bzrlib.bzrdir.BzrDir.create(shared_path)
+ shared_dir = bzrlib.bzrdir.BzrDir.create(shared_path,
+ possible_transports=transports)
try:
shared_repo = shared_dir.open_repository()
except bzrlib.errors.NoRepositoryPresent:
else:
# check and remove old organization
try:
- bdir = bzrlib.bzrdir.BzrDir.open(clone_path)
+ bdir = bzrlib.bzrdir.BzrDir.open(clone_path,
+ possible_transports=transports)
bdir.destroy_repository()
except bzrlib.errors.NotBranchError:
pass
except bzrlib.errors.NoRepositoryPresent:
pass
- wanted = get_config('remote-bzr.branches').rstrip().split(', ')
+ wanted = get_config('remote.%s.bzr-branches' % alias).rstrip().split(', ')
# stupid python
wanted = [e for e in wanted if e]
+ if not wanted:
+ wanted = get_config('remote-bzr.branches').rstrip().split(', ')
+ # stupid python
+ wanted = [e for e in wanted if e]
if not wanted:
try:
global files_cache
global is_tmp
global branches, peers
+ global transports
alias = args[1]
url = args[2]
marks = None
branches = {}
peers = {}
+ transports = []
if alias[5:] == url:
is_tmp = True
import urllib
import atexit
import urlparse, hashlib
+import time as ptime
+#
+# If you want to see Mercurial revisions as Git commit notes:
+# git config core.notesRef refs/notes/hg
#
# If you are not in hg-git-compat mode and want to disable the tracking of
# named branches:
self.rev_marks = {}
self.last_mark = 0
self.version = 0
+ self.last_note = 0
def load(self):
if not os.path.exists(self.path):
self.marks = tmp['marks']
self.last_mark = tmp['last-mark']
self.version = tmp.get('version', 1)
+ self.last_note = tmp.get('last-note', 0)
for rev, mark in self.marks.iteritems():
self.rev_marks[mark] = rev
self.version = 2
def dict(self):
- return { 'tips': self.tips, 'marks': self.marks, 'last-mark' : self.last_mark, 'version' : self.version }
+ return { 'tips': self.tips, 'marks': self.marks, 'last-mark' : self.last_mark, 'version' : self.version, 'last-note' : self.last_note }
def store(self):
json.dump(self.dict(), open(self.path, 'w'))
return sys.stdin.read(size)
def get_author(self):
- global bad_mail
-
ex = None
m = RAW_AUTHOR_RE.match(self.line)
if not m:
return os.path.relpath(path, '/')
def export_files(files):
- global marks, filenodes
-
final = []
for f in files:
fid = node.hex(f.filenode())
return (name, mail)
def fixup_user(user):
- global mode, bad_mail
-
if mode == 'git':
name, mail = fixup_user_git(user)
else:
bookmarks.write(repo)
def get_repo(url, alias):
- global dirname, peer
+ global peer
myui = ui.ui()
myui.setconfig('ui', 'interactive', 'off')
return repo
def rev_to_mark(rev):
- global marks
return marks.from_rev(rev.hex())
def mark_to_rev(mark):
- global marks
return marks.to_rev(mark)
def export_ref(repo, name, kind, head):
- global prefix, marks, mode
-
ename = '%s/%s' % (kind, name)
try:
tip = marks.get_tip(ename)
print "from :%u" % rev_to_mark(head)
print
+ pending_revs = set(revs) - notes
+ if pending_revs:
+ note_mark = marks.next_mark()
+ ref = "refs/notes/hg"
+
+ print "commit %s" % ref
+ print "mark :%d" % (note_mark)
+ print "committer remote-hg <> %s" % (ptime.strftime('%s %z'))
+ desc = "Notes for %s\n" % (name)
+ print "data %d" % (len(desc))
+ print desc
+ if marks.last_note:
+ print "from :%u" % marks.last_note
+
+ for rev in pending_revs:
+ notes.add(rev)
+ c = repo[rev]
+ print "N inline :%u" % rev_to_mark(c)
+ msg = c.hex()
+ print "data %d" % (len(msg))
+ print msg
+ print
+
+ marks.last_note = note_mark
+
marks.set_tip(ename, head.hex())
def export_tag(repo, tag):
export_ref(repo, branch, 'branches', head)
def export_head(repo):
- global g_head
export_ref(repo, g_head[0], 'bookmarks', g_head[1])
def do_capabilities(parser):
- global prefix, dirname
-
print "import"
print "export"
print "refspec refs/heads/branches/*:%s/branches/*" % prefix
return branches[branch][-1]
def get_branch_tip(repo, branch):
- global branches
-
heads = branches.get(hgref(branch), None)
if not heads:
return None
return heads[0]
def list_head(repo, cur):
- global g_head, bmarks, fake_bmark
+ global g_head, fake_bmark
if 'default' not in branches:
# empty repo
g_head = (head, node)
def do_list(parser):
- global branches, bmarks, track_branches
-
repo = parser.repo
for bmark, node in bookmarks.listbookmarks(repo).iteritems():
bmarks[bmark] = repo[node]
print 'done'
def parse_blob(parser):
- global blob_marks
-
parser.next()
mark = parser.get_mark()
parser.next()
files[e] = f
def parse_commit(parser):
- global marks, blob_marks, parsed_refs
- global mode
-
from_mark = merge_mark = None
ref = parser[1]
marks.new_mark(node, commit_mark)
def parse_reset(parser):
- global parsed_refs
-
ref = parser[1]
parser.next()
# ugh
return tip in heads
def do_export(parser):
- global parsed_refs, bmarks, peer
-
p_bmarks = []
p_revs = {}
author, msg = parsed_tags.get(tag, (None, None))
if mode == 'git':
if not msg:
- msg = 'Added tag %s for changeset %s' % (tag, node[:12]);
+ msg = 'Added tag %s for changeset %s' % (tag, node[:12])
tagnode, branch = write_tag(parser.repo, tag, node, msg, author)
p_revs[tagnode] = 'refs/heads/branches/' + gitref(branch)
else:
global filenodes
global fake_bmark, hg_version
global dry_run
+ global notes, alias
alias = args[1]
url = args[2]
except:
hg_version = None
dry_run = False
+ notes = set()
repo = get_repo(url, alias)
prefix = 'refs/hg/%s' % alias
. ./test-lib.sh
-if ! test_have_prereq PYTHON; then
+if ! test_have_prereq PYTHON
+then
skip_all='skipping remote-bzr tests; python not available'
test_done
fi
-if ! python -c 'import bzrlib'; then
+if ! python -c 'import bzrlib'
+then
skip_all='skipping remote-bzr tests; bzr not available'
test_done
fi
check () {
- echo $3 > expected &&
- git --git-dir=$1/.git log --format='%s' -1 $2 > actual
+ echo $3 >expected &&
+ git --git-dir=$1/.git log --format='%s' -1 $2 >actual
test_cmp expected actual
}
(
bzr init bzrrepo &&
cd bzrrepo &&
- echo one > content &&
+ echo one >content &&
bzr add content &&
bzr commit -m one
) &&
test_expect_success 'pulling' '
(
cd bzrrepo &&
- echo two > content &&
+ echo two >content &&
bzr commit -m two
) &&
test_expect_success 'pushing' '
(
cd gitrepo &&
- echo three > content &&
+ echo three >content &&
git commit -a -m three &&
git push
) &&
- echo three > expected &&
- cat bzrrepo/content > actual &&
+ echo three >expected &&
+ cat bzrrepo/content >actual &&
test_cmp expected actual
'
(
cd gitrepo &&
git pull &&
- git log --format="%s" -1 origin/master > actual
+ git log --format="%s" -1 origin/master >actual
) &&
- echo three > expected &&
+ echo three >expected &&
test_cmp expected actual &&
(cd gitrepo && git push && git pull) &&
(
cd bzrrepo &&
- echo four > content &&
+ echo four >content &&
bzr commit -m four
) &&
(
cd gitrepo &&
- echo five > content &&
+ echo five >content &&
git commit -a -m five &&
git push && git pull
) &&
(cd bzrrepo && bzr revert) &&
- echo five > expected &&
- cat bzrrepo/content > actual &&
+ echo five >expected &&
+ cat bzrrepo/content >actual &&
test_cmp expected actual
'
-cat > expected <<EOF
+cat >expected <<\EOF
100644 blob 54f9d6da5c91d556e6b54340b1327573073030af content
100755 blob 68769579c3eaadbe555379b9c3538e6628bae1eb executable
120000 blob 6b584e8ece562ebffc15d38808cd6b98fc3d97ea link
test_expect_success 'special modes' '
(
cd bzrrepo &&
- echo exec > executable
+ echo exec >executable
chmod +x executable &&
bzr add executable
bzr commit -m exec &&
(
cd gitrepo &&
git pull
- git ls-tree HEAD > ../actual
+ git ls-tree HEAD >../actual
) &&
test_cmp expected actual &&
(
cd gitrepo &&
- git cat-file -p HEAD:link > ../actual
+ git cat-file -p HEAD:link >../actual
) &&
- printf content > expected &&
+ printf content >expected &&
test_cmp expected actual
'
-cat > expected <<EOF
+cat >expected <<\EOF
100644 blob 54f9d6da5c91d556e6b54340b1327573073030af content
100755 blob 68769579c3eaadbe555379b9c3538e6628bae1eb executable
120000 blob 6b584e8ece562ebffc15d38808cd6b98fc3d97ea link
(
cd bzrrepo &&
mkdir movedir &&
- echo one > movedir/one &&
- echo two > movedir/two &&
+ echo one >movedir/one &&
+ echo two >movedir/two &&
bzr add movedir &&
bzr commit -m movedir &&
bzr mv movedir movedir-new &&
(
cd gitrepo &&
git pull &&
- git ls-tree HEAD > ../actual
+ git ls-tree HEAD >../actual
) &&
test_cmp expected actual
test_expect_success 'different authors' '
(
cd bzrrepo &&
- echo john >> content &&
+ echo john >>content &&
bzr commit -m john \
--author "Jane Rey <jrey@example.com>" \
--author "John Doe <jdoe@example.com>"
(
cd gitrepo &&
git pull &&
- git show --format="%an <%ae>, %cn <%ce>" --quiet > ../actual
+ git show --format="%an <%ae>, %cn <%ce>" --quiet >../actual
) &&
- echo "Jane Rey <jrey@example.com>, A U Thor <author@example.com>" > expected &&
+ echo "Jane Rey <jrey@example.com>, A U Thor <author@example.com>" >expected &&
test_cmp expected actual
'
bzr init bzrrepo &&
cd bzrrepo &&
- echo test >> "ærø" &&
+ echo test >>"ærø" &&
bzr add "ærø" &&
- echo test >> "ø~?" &&
+ echo test >>"ø~?" &&
bzr add "ø~?" &&
bzr commit -m add-utf-8 &&
- echo test >> "ærø" &&
+ echo test >>"ærø" &&
bzr commit -m test-utf-8 &&
bzr rm "ø~?" &&
bzr mv "ærø" "ø~?" &&
(
git clone "bzr::bzrrepo" gitrepo &&
cd gitrepo &&
- git -c core.quotepath=false ls-files > ../actual
+ git -c core.quotepath=false ls-files >../actual
) &&
- echo "ø~?" > expected &&
+ echo "ø~?" >expected &&
test_cmp expected actual
'
bzr init bzrrepo &&
cd bzrrepo &&
- echo one >> content &&
+ echo one >>content &&
bzr add content &&
bzr commit -m one
) &&
git clone "bzr::bzrrepo" gitrepo &&
cd gitrepo &&
- echo test >> "ærø" &&
+ echo test >>"ærø" &&
git add "ærø" &&
git commit -m utf-8 &&
git push
) &&
- (cd bzrrepo && bzr ls > ../actual) &&
- printf "content\nærø\n" > expected &&
+ (cd bzrrepo && bzr ls >../actual) &&
+ printf "content\nærø\n" >expected &&
test_cmp expected actual
'
(
bzr init bzrrepo &&
cd bzrrepo &&
- echo one > content &&
+ echo one >content &&
bzr add content &&
bzr commit -m one
) &&
(
cd bzrrepo &&
- echo two > content &&
+ echo two >content &&
bzr commit -m two
) &&
(
cd gitrepo &&
- echo three > content &&
+ echo three >content &&
git commit -a -m three &&
git fetch &&
git merge origin/master || true &&
- echo three > content &&
+ echo three >content &&
git commit -a --no-edit &&
git push
) &&
- echo three > expected &&
- cat bzrrepo/content > actual &&
+ echo three >expected &&
+ cat bzrrepo/content >actual &&
test_cmp expected actual
'
-cat > expected <<EOF
+cat >expected <<\EOF
origin/HEAD
origin/branch
origin/trunk
(
bzr init bzrrepo/trunk &&
cd bzrrepo/trunk &&
- echo one >> content &&
+ echo one >>content &&
bzr add content &&
bzr commit -m one
) &&
(
bzr branch bzrrepo/trunk bzrrepo/branch &&
cd bzrrepo/branch &&
- echo two >> content &&
+ echo two >>content &&
bzr commit -m one
) &&
(
git clone "bzr::bzrrepo" gitrepo &&
cd gitrepo &&
- git for-each-ref --format "%(refname:short)" refs/remotes/origin > ../actual
+ git for-each-ref --format "%(refname:short)" refs/remotes/origin >../actual
) &&
test_cmp expected actual
bzr init bzrrepo &&
cd bzrrepo &&
- echo one >> content &&
+ echo one >>content &&
bzr add content &&
bzr commit -m one &&
- echo two >> content &&
+ echo two >>content &&
bzr commit -m two
) &&
cd bzrrepo &&
bzr uncommit --force &&
- echo three >> content &&
+ echo three >>content &&
bzr commit -m three &&
- echo four >> content &&
+ echo four >>content &&
bzr commit -m four &&
- bzr log --line | sed -e "s/^[0-9][0-9]*: //" > ../expected
+ bzr log --line | sed -e "s/^[0-9][0-9]*: //" >../expected
) &&
(
cd gitrepo &&
git fetch &&
- git log --format="%an %ad %s" --date=short origin/master > ../actual
+ git log --format="%an %ad %s" --date=short origin/master >../actual
) &&
test_cmp expected actual
'
+test_expect_success 'export utf-8 authors' '
+ test_when_finished "rm -rf bzrrepo gitrepo && LC_ALL=C && unset GIT_COMMITTER_NAME" &&
+
+ LC_ALL=en_US.UTF-8
+ export LC_ALL
+
+ GIT_COMMITTER_NAME="Grégoire"
+ export GIT_COMMITTER_NAME
+
+ bzr init bzrrepo &&
+
+ (
+ git init gitrepo &&
+ cd gitrepo &&
+ echo greg >>content &&
+ git add content &&
+ git commit -m one &&
+ git remote add bzr "bzr::../bzrrepo" &&
+ git push bzr
+ ) &&
+
+ (
+ cd bzrrepo &&
+ bzr log | grep "^committer: " >../actual
+ ) &&
+
+ echo "committer: Grégoire <committer@example.com>" >expected &&
+ test_cmp expected actual
+'
+
test_done
. ./test-lib.sh
-if ! test_have_prereq PYTHON; then
+if ! test_have_prereq PYTHON
+then
skip_all='skipping remote-hg tests; python not available'
test_done
fi
-if ! python -c 'import mercurial'; then
+if ! python -c 'import mercurial'
+then
skip_all='skipping remote-hg tests; mercurial not available'
test_done
fi
git checkout -q -b tmp &&
git fetch -q "hg::../$1" 'refs/tags/*:refs/tags/*' 'refs/heads/*:refs/heads/*' &&
git checkout -q @{-1} &&
- git branch -q -D tmp 2> /dev/null || true
+ git branch -q -D tmp 2>/dev/null || true
)
}
echo "tag = -d \"0 0\""
echo "[extensions]"
echo "graphlog ="
- ) >> "$HOME"/.hgrc &&
+ ) >>"$HOME"/.hgrc &&
git config --global remote-hg.hg-git-compat true
git config --global remote-hg.track-branches true
git init -q gitrepo &&
cd gitrepo &&
- echo alpha > alpha &&
+ echo alpha >alpha &&
git add alpha &&
git commit -m "add älphà" &&
GIT_AUTHOR_NAME="tést èncödîng" &&
export GIT_AUTHOR_NAME &&
- echo beta > beta &&
+ echo beta >beta &&
git add beta &&
git commit -m "add beta" &&
- echo gamma > gamma &&
+ echo gamma >gamma &&
git add gamma &&
git commit -m "add gämmâ" &&
: TODO git config i18n.commitencoding latin-1 &&
- echo delta > delta &&
+ echo delta >delta &&
git add delta &&
git commit -m "add déltà"
) &&
git_clone hgrepo gitrepo2 &&
hg_clone gitrepo2 hgrepo2 &&
- HGENCODING=utf-8 hg_log hgrepo > expected &&
- HGENCODING=utf-8 hg_log hgrepo2 > actual &&
+ HGENCODING=utf-8 hg_log hgrepo >expected &&
+ HGENCODING=utf-8 hg_log hgrepo2 >actual &&
test_cmp expected actual
'
(
git init -q gitrepo &&
cd gitrepo &&
- echo alpha > alpha &&
+ echo alpha >alpha &&
git add alpha &&
git commit -m "add alpha" &&
- echo beta > beta &&
+ echo beta >beta &&
git add beta &&
git commit -m "add beta"
mkdir foo &&
- echo blah > foo/bar &&
+ echo blah >foo/bar &&
git add foo &&
git commit -m "add foo" &&
git rm alpha &&
git_clone hgrepo gitrepo2 &&
hg_clone gitrepo2 hgrepo2 &&
- hg_log hgrepo > expected &&
- hg_log hgrepo2 > actual &&
+ hg_log hgrepo >expected &&
+ hg_log hgrepo2 >actual &&
test_cmp expected actual
'
git init -q gitrepo &&
cd gitrepo &&
git config receive.denyCurrentBranch ignore &&
- echo alpha > alpha &&
+ echo alpha >alpha &&
git add alpha &&
git commit -m "add alpha" &&
git tag alpha &&
- echo beta > beta &&
+ echo beta >beta &&
git add beta &&
git commit -m "add beta" &&
git tag -a -m "added tag beta" beta
git_clone hgrepo gitrepo2 &&
hg_clone gitrepo2 hgrepo2 &&
- hg_log hgrepo > expected &&
- hg_log hgrepo2 > actual &&
+ hg_log hgrepo >expected &&
+ hg_log hgrepo2 >actual &&
test_cmp expected actual
'
git init -q gitrepo &&
cd gitrepo &&
- echo alpha > alpha &&
+ echo alpha >alpha &&
git add alpha &&
git commit -q -m "add alpha" &&
git checkout -q -b not-master
: Back to the common revision &&
(cd hgrepo && hg checkout default) &&
- hg_log hgrepo > expected &&
- hg_log hgrepo2 > actual &&
+ hg_log hgrepo >expected &&
+ hg_log hgrepo2 >actual &&
test_cmp expected actual
'
git init -q gitrepo &&
cd gitrepo &&
- echo alpha > alpha &&
+ echo alpha >alpha &&
git add alpha &&
git commit -m "add alpha" &&
git checkout -q -b not-master
hg_push hgrepo gitrepo &&
hg_clone gitrepo hgrepo2 &&
- hg_log hgrepo > expected &&
- hg_log hgrepo2 > actual &&
+ hg_log hgrepo >expected &&
+ hg_log hgrepo2 >actual &&
test_cmp expected actual
'
. ./test-lib.sh
-if ! test_have_prereq PYTHON; then
+if ! test_have_prereq PYTHON
+then
skip_all='skipping remote-hg tests; python not available'
test_done
fi
-if ! python -c 'import mercurial'; then
+if ! python -c 'import mercurial'
+then
skip_all='skipping remote-hg tests; mercurial not available'
test_done
fi
-if ! python -c 'import hggit'; then
+if ! python -c 'import hggit'
+then
skip_all='skipping remote-hg tests; hg-git not available'
test_done
fi
git fetch -q "hg::../$1" 'refs/tags/*:refs/tags/*' 'refs/heads/*:refs/heads/*' &&
git branch -D default &&
git checkout -q @{-1} &&
- git branch -q -D tmp 2> /dev/null || true
+ git branch -q -D tmp 2>/dev/null || true
)
}
echo "hgext.bookmarks ="
echo "hggit ="
echo "graphlog ="
- ) >> "$HOME"/.hgrc &&
+ ) >>"$HOME"/.hgrc &&
git config --global receive.denycurrentbranch warn
git config --global remote-hg.hg-git-compat true
git config --global remote-hg.track-branches false
(
git init -q gitrepo &&
cd gitrepo &&
- echo alpha > alpha &&
+ echo alpha >alpha &&
chmod 0644 alpha &&
git add alpha &&
git commit -m "add alpha" &&
git commit -m "clear executable bit"
) &&
- for x in hg git; do
+ for x in hg git
+ do
(
hg_clone_$x gitrepo hgrepo-$x &&
cd hgrepo-$x &&
hg_log . &&
hg manifest -r 1 -v &&
hg manifest -v
- ) > output-$x &&
+ ) >"output-$x" &&
git_clone_$x hgrepo-$x gitrepo2-$x &&
- git_log gitrepo2-$x > log-$x
+ git_log gitrepo2-$x >"log-$x"
done &&
test_cmp output-hg output-git &&
(
git init -q gitrepo &&
cd gitrepo &&
- echo alpha > alpha &&
+ echo alpha >alpha &&
git add alpha &&
git commit -m "add alpha" &&
ln -s alpha beta &&
git commit -m "add beta"
) &&
- for x in hg git; do
+ for x in hg git
+ do
(
hg_clone_$x gitrepo hgrepo-$x &&
cd hgrepo-$x &&
hg_log . &&
hg manifest -v
- ) > output-$x &&
+ ) >"output-$x" &&
git_clone_$x hgrepo-$x gitrepo2-$x &&
- git_log gitrepo2-$x > log-$x
+ git_log gitrepo2-$x >"log-$x"
done &&
test_cmp output-hg output-git &&
(
hg init hgrepo1 &&
cd hgrepo1 &&
- echo A > afile &&
+ echo A >afile &&
hg add afile &&
hg ci -m "origin" &&
- echo B > afile &&
+ echo B >afile &&
hg ci -m "A->B" &&
hg up -r0 &&
- echo C > afile &&
+ echo C >afile &&
hg ci -m "A->C" &&
hg merge -r1 &&
- echo C > afile &&
+ echo C >afile &&
hg resolve -m afile &&
hg ci -m "merge to C"
) &&
- for x in hg git; do
+ for x in hg git
+ do
git_clone_$x hgrepo1 gitrepo-$x &&
hg_clone_$x gitrepo-$x hgrepo2-$x &&
- hg_log hgrepo2-$x > hg-log-$x &&
- git_log gitrepo-$x > git-log-$x
+ hg_log hgrepo2-$x >"hg-log-$x" &&
+ git_log gitrepo-$x >"git-log-$x"
done &&
test_cmp hg-log-hg hg-log-git &&
(
hg init hgrepo1 &&
cd hgrepo1 &&
- echo A > afile &&
+ echo A >afile &&
hg add afile &&
hg ci -m "origin" &&
- echo B > afile &&
+ echo B >afile &&
hg ci -m "A->B" &&
hg up -r0 &&
- echo C > afile &&
+ echo C >afile &&
hg ci -m "A->C" &&
hg merge -r1 || true &&
- echo B > afile &&
+ echo B >afile &&
hg resolve -m afile &&
hg ci -m "merge to B"
) &&
- for x in hg git; do
+ for x in hg git
+ do
git_clone_$x hgrepo1 gitrepo-$x &&
hg_clone_$x gitrepo-$x hgrepo2-$x &&
- hg_log hgrepo2-$x > hg-log-$x &&
- git_log gitrepo-$x > git-log-$x
+ hg_log hgrepo2-$x >"hg-log-$x" &&
+ git_log gitrepo-$x >"git-log-$x"
done &&
test_cmp hg-log-hg hg-log-git &&
(
hg init hgrepo1 &&
cd hgrepo1 &&
- echo A > afile &&
+ echo A >afile &&
hg add afile &&
hg ci -m "origin" &&
- echo B > afile &&
+ echo B >afile &&
hg ci -m "A->B" &&
- echo C > afile &&
+ echo C >afile &&
hg ci -m "B->C" &&
hg up -r0 &&
- echo C > afile &&
+ echo C >afile &&
hg ci -m "A->C" &&
hg merge -r2 || true &&
hg ci -m "merge"
) &&
- for x in hg git; do
+ for x in hg git
+ do
git_clone_$x hgrepo1 gitrepo-$x &&
hg_clone_$x gitrepo-$x hgrepo2-$x &&
- hg_log hgrepo2-$x > hg-log-$x &&
- git_log gitrepo-$x > git-log-$x
+ hg_log hgrepo2-$x >"hg-log-$x" &&
+ git_log gitrepo-$x >"git-log-$x"
done &&
test_cmp hg-log-hg hg-log-git &&
git init -q gitrepo &&
cd gitrepo &&
- echo alpha > alpha &&
+ echo alpha >alpha &&
git add alpha &&
git commit -m "add älphà" &&
GIT_AUTHOR_NAME="tést èncödîng" &&
export GIT_AUTHOR_NAME &&
- echo beta > beta &&
+ echo beta >beta &&
git add beta &&
git commit -m "add beta" &&
- echo gamma > gamma &&
+ echo gamma >gamma &&
git add gamma &&
git commit -m "add gämmâ" &&
: TODO git config i18n.commitencoding latin-1 &&
- echo delta > delta &&
+ echo delta >delta &&
git add delta &&
git commit -m "add déltà"
) &&
- for x in hg git; do
+ for x in hg git
+ do
hg_clone_$x gitrepo hgrepo-$x &&
git_clone_$x hgrepo-$x gitrepo2-$x &&
- HGENCODING=utf-8 hg_log hgrepo-$x > hg-log-$x &&
- git_log gitrepo2-$x > git-log-$x
+ HGENCODING=utf-8 hg_log hgrepo-$x >"hg-log-$x" &&
+ git_log gitrepo2-$x >"git-log-$x"
done &&
test_cmp hg-log-hg hg-log-git &&
(
git init -q gitrepo &&
cd gitrepo &&
- echo alpha > alpha &&
+ echo alpha >alpha &&
git add alpha &&
git commit -m "add alpha" &&
- echo beta > beta &&
+ echo beta >beta &&
git add beta &&
git commit -m "add beta"
mkdir foo &&
- echo blah > foo/bar &&
+ echo blah >foo/bar &&
git add foo &&
git commit -m "add foo" &&
git rm alpha &&
git commit -m "remove foo/bar"
) &&
- for x in hg git; do
+ for x in hg git
+ do
(
hg_clone_$x gitrepo hgrepo-$x &&
cd hgrepo-$x &&
hg_log . &&
hg manifest -r 3 &&
hg manifest
- ) > output-$x &&
+ ) >"output-$x" &&
git_clone_$x hgrepo-$x gitrepo2-$x &&
- git_log gitrepo2-$x > log-$x
+ git_log gitrepo2-$x >"log-$x"
done &&
test_cmp output-hg output-git &&
git init -q gitrepo &&
cd gitrepo &&
git config receive.denyCurrentBranch ignore &&
- echo alpha > alpha &&
+ echo alpha >alpha &&
git add alpha &&
git commit -m "add alpha" &&
git tag alpha &&
- echo beta > beta &&
+ echo beta >beta &&
git add beta &&
git commit -m "add beta" &&
git tag -a -m "added tag beta" beta
) &&
- for x in hg git; do
+ for x in hg git
+ do
hg_clone_$x gitrepo hgrepo-$x &&
- hg_log hgrepo-$x > log-$x
+ hg_log hgrepo-$x >"log-$x"
done &&
test_cmp log-hg log-git
test_expect_success 'hg author' '
test_when_finished "rm -rf gitrepo* hgrepo*" &&
- for x in hg git; do
+ for x in hg git
+ do
(
git init -q gitrepo-$x &&
cd gitrepo-$x &&
- echo alpha > alpha &&
+ echo alpha >alpha &&
git add alpha &&
git commit -m "add alpha" &&
git checkout -q -b not-master
cd hgrepo-$x &&
hg co master &&
- echo beta > beta &&
+ echo beta >beta &&
hg add beta &&
hg commit -u "test" -m "add beta" &&
- echo gamma >> beta &&
+ echo gamma >>beta &&
hg commit -u "test <test@example.com> (comment)" -m "modify beta" &&
- echo gamma > gamma &&
+ echo gamma >gamma &&
hg add gamma &&
hg commit -u "<test@example.com>" -m "add gamma" &&
- echo delta > delta &&
+ echo delta >delta &&
hg add delta &&
hg commit -u "name<test@example.com>" -m "add delta" &&
- echo epsilon > epsilon &&
+ echo epsilon >epsilon &&
hg add epsilon &&
hg commit -u "name <test@example.com" -m "add epsilon" &&
- echo zeta > zeta &&
+ echo zeta >zeta &&
hg add zeta &&
hg commit -u " test " -m "add zeta" &&
- echo eta > eta &&
+ echo eta >eta &&
hg add eta &&
hg commit -u "test < test@example.com >" -m "add eta" &&
- echo theta > theta &&
+ echo theta >theta &&
hg add theta &&
hg commit -u "test >test@example.com>" -m "add theta" &&
- echo iota > iota &&
+ echo iota >iota &&
hg add iota &&
hg commit -u "test <test <at> example <dot> com>" -m "add iota"
) &&
hg_push_$x hgrepo-$x gitrepo-$x &&
hg_clone_$x gitrepo-$x hgrepo2-$x &&
- hg_log hgrepo2-$x > hg-log-$x &&
- git_log gitrepo-$x > git-log-$x
+ hg_log hgrepo2-$x >"hg-log-$x" &&
+ git_log gitrepo-$x >"git-log-$x"
done &&
test_cmp hg-log-hg hg-log-git &&
test_expect_success 'hg branch' '
test_when_finished "rm -rf gitrepo* hgrepo*" &&
- for x in hg git; do
+ for x in hg git
+ do
(
git init -q gitrepo-$x &&
cd gitrepo-$x &&
- echo alpha > alpha &&
+ echo alpha >alpha &&
git add alpha &&
git commit -q -m "add alpha" &&
git checkout -q -b not-master
hg_push_$x hgrepo-$x gitrepo-$x &&
hg_clone_$x gitrepo-$x hgrepo2-$x &&
- hg_log hgrepo2-$x > hg-log-$x &&
- git_log gitrepo-$x > git-log-$x
+ hg_log hgrepo2-$x >"hg-log-$x" &&
+ git_log gitrepo-$x >"git-log-$x"
done &&
test_cmp hg-log-hg hg-log-git &&
test_expect_success 'hg tags' '
test_when_finished "rm -rf gitrepo* hgrepo*" &&
- for x in hg git; do
+ for x in hg git
+ do
(
git init -q gitrepo-$x &&
cd gitrepo-$x &&
- echo alpha > alpha &&
+ echo alpha >alpha &&
git add alpha &&
git commit -m "add alpha" &&
git checkout -q -b not-master
git --git-dir=gitrepo-$x/.git tag -l &&
hg_log hgrepo2-$x &&
cat hgrepo2-$x/.hgtags
- ) > output-$x
+ ) >"output-$x"
done &&
test_cmp output-hg output-git
. ./test-lib.sh
-if ! test_have_prereq PYTHON; then
+if ! test_have_prereq PYTHON
+then
skip_all='skipping remote-hg tests; python not available'
test_done
fi
-if ! python -c 'import mercurial'; then
+if ! python -c 'import mercurial'
+then
skip_all='skipping remote-hg tests; mercurial not available'
test_done
fi
check () {
- echo $3 > expected &&
- git --git-dir=$1/.git log --format='%s' -1 $2 > actual
+ echo $3 >expected &&
+ git --git-dir=$1/.git log --format='%s' -1 $2 >actual
test_cmp expected actual
}
check_branch () {
- if [ -n "$3" ]; then
- echo $3 > expected &&
- hg -R $1 log -r $2 --template '{desc}\n' > actual &&
+ if test -n "$3"
+ then
+ echo $3 >expected &&
+ hg -R $1 log -r $2 --template '{desc}\n' >actual &&
test_cmp expected actual
else
- hg -R $1 branches > out &&
+ hg -R $1 branches >out &&
! grep $2 out
fi
}
check_bookmark () {
- if [ -n "$3" ]; then
- echo $3 > expected &&
- hg -R $1 log -r "bookmark('$2')" --template '{desc}\n' > actual &&
+ if test -n "$3"
+ then
+ echo $3 >expected &&
+ hg -R $1 log -r "bookmark('$2')" --template '{desc}\n' >actual &&
test_cmp expected actual
else
- hg -R $1 bookmarks > out &&
+ hg -R $1 bookmarks >out &&
! grep $2 out
fi
}
local expected_ret=$1 ret=0 ref_ret=0 IFS=':'
shift
- git push origin "$@" 2> error
+ git push origin "$@" 2>error
ret=$?
cat error
grep "^ [a-f0-9]*\.\.[a-f0-9]* *${branch} -> ${branch}$" error || ref_ret=1
;;
esac
- let 'ref_ret' && echo "match for '$branch' failed" && break
+ test $ref_ret -ne 0 && echo "match for '$branch' failed" && break
done
- if let 'expected_ret != ret || ref_ret'
+ if test $expected_ret -ne $ret -o $ref_ret -ne 0
then
return 1
fi
echo "username = H G Wells <wells@example.com>"
echo "[extensions]"
echo "mq ="
- ) >> "$HOME"/.hgrc &&
+ ) >>"$HOME"/.hgrc &&
GIT_AUTHOR_DATE="2007-01-01 00:00:00 +0230" &&
GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE" &&
(
hg init hgrepo &&
cd hgrepo &&
- echo zero > content &&
+ echo zero >content &&
hg add content &&
hg commit -m zero
) &&
(
cd hgrepo &&
hg branch next &&
- echo next > content &&
+ echo next >content &&
hg commit -m next
) &&
cd hgrepo &&
hg checkout default &&
hg bookmark feature-a &&
- echo feature-a > content &&
+ echo feature-a >content &&
hg commit -m feature-a
) &&
git clone "hg::hgrepo" gitrepo &&
cd gitrepo &&
git checkout --quiet devel &&
- echo devel > content &&
+ echo devel >content &&
git commit -a -m devel &&
git push --quiet
) &&
git clone "hg::hgrepo" gitrepo &&
cd gitrepo &&
git checkout --quiet -b feature-b &&
- echo feature-b > content &&
+ echo feature-b >content &&
git commit -a -m feature-b &&
git push --quiet origin feature-b
) &&
rm -rf hgrepo
author_test () {
- echo $1 >> content &&
+ echo $1 >>content &&
hg commit -u "$2" -m "add $1" &&
- echo "$3" >> ../expected
+ echo "$3" >>../expected
}
test_expect_success 'authors' '
touch content &&
hg add content &&
- > ../expected &&
+ >../expected &&
author_test alpha "" "H G Wells <wells@example.com>" &&
author_test beta "test" "test <unknown>" &&
author_test beta "test <test@example.com> (comment)" "test <test@example.com>" &&
) &&
git clone "hg::hgrepo" gitrepo &&
- git --git-dir=gitrepo/.git log --reverse --format="%an <%ae>" > actual &&
+ git --git-dir=gitrepo/.git log --reverse --format="%an <%ae>" >actual &&
test_cmp expected actual
'
hg init hgrepo &&
cd hgrepo &&
- echo one >> content &&
+ echo one >>content &&
hg add content &&
hg commit -m one &&
- echo two >> content &&
+ echo two >>content &&
hg commit -m two
) &&
cd hgrepo &&
hg strip 1 &&
- echo three >> content &&
+ echo three >>content &&
hg commit -m three &&
- echo four >> content &&
+ echo four >>content &&
hg commit -m four
) &&
(
cd gitrepo &&
git fetch &&
- git log --format="%s" origin/master > ../actual
+ git log --format="%s" origin/master >../actual
) &&
- hg -R hgrepo log --template "{desc}\n" > expected &&
+ hg -R hgrepo log --template "{desc}\n" >expected &&
test_cmp actual expected
'
(
hg init hgrepo &&
cd hgrepo &&
- echo zero > content &&
+ echo zero >content &&
hg add content &&
hg commit -m zero &&
hg bookmark master &&
- echo one > content &&
+ echo one >content &&
hg commit -m one
) &&
(
git clone "hg::hgrepo" gitrepo &&
cd gitrepo &&
- echo two > content &&
+ echo two >content &&
git commit -a -m two &&
git push
) &&
check_branch hgrepo default two
'
-cat > expected <<EOF
+cat >expected <<\EOF
changeset: 0:6e2126489d3d
tag: tip
user: A U Thor <author@example.com>
git init gitrepo &&
cd gitrepo &&
git remote add origin "hg::../hgrepo" &&
- echo one > content &&
+ echo one >content &&
git add content &&
git commit -a -m one &&
git push origin master
) &&
- hg -R hgrepo log > actual &&
+ hg -R hgrepo log >actual &&
cat actual &&
test_cmp expected actual &&
(
hg init hgrepo &&
cd hgrepo &&
- echo zero > content &&
+ echo zero >content &&
hg add content &&
hg commit -m zero
) &&
git clone "hg::hgrepo" gitrepo &&
cd gitrepo &&
git checkout --quiet devel &&
- echo devel > content &&
+ echo devel >content &&
git commit -a -m devel &&
git push --quiet
) &&
git clone "hg::hgrepo" gitrepo &&
cd gitrepo &&
git checkout --quiet -b feature-b &&
- echo feature-b > content &&
+ echo feature-b >content &&
git commit -a -m feature-b &&
git push --quiet origin feature-b
) &&
(
cd hgrepo &&
hg checkout default &&
- echo bump > content &&
+ echo bump >content &&
hg commit -m bump
) &&
(
cd gitrepo &&
- echo diverge > content &&
+ echo diverge >content &&
git commit -a -m diverged &&
- check_push 1 <<-EOF
+ check_push 1 <<-\EOF
master:non-fast-forward
EOF
) &&
(
cd hgrepo &&
- echo "bump bookmark" > content &&
+ echo "bump bookmark" >content &&
hg commit -m "bump bookmark"
) &&
(
cd gitrepo &&
git checkout --quiet diverge &&
- echo diverge > content &&
+ echo diverge >content &&
git commit -a -m diverge &&
- check_push 1 <<-EOF
+ check_push 1 <<-\EOF
diverge:fetch-first
EOF
) &&
git clone "hg::hgrepo" gitrepo &&
cd gitrepo &&
git checkout --quiet -b feature-c HEAD^ &&
- echo feature-c > content &&
+ echo feature-c >content &&
git commit -a -m feature-c &&
git push --quiet origin feature-c
) &&
(
hg init hgrepo &&
cd hgrepo &&
- echo zero > content &&
+ echo zero >content &&
hg add content &&
hg commit -m zero &&
hg bookmark bad_bmark1 &&
- echo one > content &&
+ echo one >content &&
hg commit -m one &&
hg bookmark bad_bmark2 &&
hg bookmark good_bmark &&
hg bookmark -i good_bmark &&
hg -q branch good_branch &&
- echo "good branch" > content &&
+ echo "good branch" >content &&
hg commit -m "good branch" &&
hg -q branch bad_branch &&
- echo "bad branch" > content &&
+ echo "bad branch" >content &&
hg commit -m "bad branch"
) &&
(
cd gitrepo &&
- echo two > content &&
+ echo two >content &&
git commit -q -a -m two &&
git checkout -q good_bmark &&
- echo three > content &&
+ echo three >content &&
git commit -q -a -m three &&
git checkout -q bad_bmark1 &&
git reset --hard HEAD^ &&
- echo four > content &&
+ echo four >content &&
git commit -q -a -m four &&
git checkout -q bad_bmark2 &&
git reset --hard HEAD^ &&
- echo five > content &&
+ echo five >content &&
git commit -q -a -m five &&
git checkout -q -b new_bmark master &&
- echo six > content &&
+ echo six >content &&
git commit -q -a -m six &&
git checkout -q branches/good_branch &&
- echo seven > content &&
+ echo seven >content &&
git commit -q -a -m seven &&
- echo eight > content &&
+ echo eight >content &&
git commit -q -a -m eight &&
git checkout -q branches/bad_branch &&
git reset --hard HEAD^ &&
- echo nine > content &&
+ echo nine >content &&
git commit -q -a -m nine &&
git checkout -q -b branches/new_branch master &&
- echo ten > content &&
+ echo ten >content &&
git commit -q -a -m ten
)
}
(
cd gitrepo &&
- check_push 1 --all <<-EOF
+ check_push 1 --all <<-\EOF
master
good_bmark
branches/good_branch
(
hg init hgrepo &&
cd hgrepo &&
- echo zero > content &&
+ echo zero >content &&
hg add content &&
hg commit -m zero &&
hg bookmark bad_bmark &&
hg bookmark good_bmark &&
hg bookmark -i good_bmark &&
hg -q branch good_branch &&
- echo "good branch" > content &&
+ echo "good branch" >content &&
hg commit -m "good branch" &&
hg -q branch bad_branch &&
- echo "bad branch" > content &&
+ echo "bad branch" >content &&
hg commit -m "bad branch"
) &&
(
cd hgrepo &&
hg bookmark -f bad_bmark &&
- echo update_bmark > content &&
+ echo update_bmark >content &&
hg commit -m "update bmark"
) &&
(
cd gitrepo &&
- echo two > content &&
+ echo two >content &&
git commit -q -a -m two &&
git checkout -q good_bmark &&
- echo three > content &&
+ echo three >content &&
git commit -q -a -m three &&
git checkout -q bad_bmark &&
- echo four > content &&
+ echo four >content &&
git commit -q -a -m four &&
git checkout -q branches/bad_branch &&
- echo five > content &&
+ echo five >content &&
git commit -q -a -m five &&
- check_push 1 --all <<-EOF
+ check_push 1 --all <<-\EOF &&
master
good_bmark
- new_bmark:new
- new_branch:new
bad_bmark:fetch-first
branches/bad_branch:festch-first
EOF
git fetch &&
- check_push 1 --all <<-EOF
+ check_push 1 --all <<-\EOF
master
good_bmark
bad_bmark:non-fast-forward
(
cd gitrepo &&
- check_push 0 --force --all <<-EOF
+ check_push 0 --force --all <<-\EOF
master
good_bmark
branches/good_branch
(
cd gitrepo &&
- check_push 0 --dry-run --all <<-EOF
+ check_push 1 --dry-run --all <<-\EOF &&
master
good_bmark
branches/good_branch
branches/bad_branch:non-fast-forward
EOF
- check_push 0 --dry-run master good_bmark new_bmark branches/good_branch branches/new_branch <<-EOF
+ check_push 0 --dry-run master good_bmark new_bmark branches/good_branch branches/new_branch <<-\EOF
master
good_bmark
branches/good_branch
(
hg init hgrepo &&
cd hgrepo &&
- echo zero > content &&
+ echo zero >content &&
hg add content &&
hg commit -m zero &&
- echo one > content &&
+ echo one >content &&
hg commit -m one
) &&
git clone "hg::hgrepo" gitrepo &&
cd gitrepo &&
git reset --hard HEAD^ &&
- echo two > content &&
+ echo two >content &&
git commit -a -m two &&
test_expect_code 1 git push &&
test_expect_code 1 git push
{
int entries, i;
int diff_unmerged_stage = revs->max_count;
- int silent_on_removed = option & DIFF_SILENT_ON_REMOVED;
unsigned ce_option = ((option & DIFF_RACY_IS_MODIFIED)
? CE_MATCH_RACY_IS_DIRTY : 0);
+ if (option & DIFF_SILENT_ON_REMOVED)
+ handle_deprecated_show_diff_q(&revs->diffopt);
+
diff_set_mnemonic_prefix(&revs->diffopt, "i/", "w/");
if (diff_unmerged_stage < 0)
perror(ce->name);
continue;
}
- if (silent_on_removed)
- continue;
wt_mode = 0;
}
dpath->mode = wt_mode;
perror(ce->name);
continue;
}
- if (silent_on_removed)
- continue;
diff_addremove(&revs->diffopt, '-', ce->ce_mode,
ce->sha1, !is_null_sha1(ce->sha1),
ce->name, 0);
opts.dst_index = NULL;
opts.pathspec = &revs->diffopt.pathspec;
opts.pathspec->recursive = 1;
- opts.pathspec->max_depth = -1;
init_tree_desc(&t, tree->buffer, tree->size);
return unpack_trees(1, &t, &opts);
struct rev_info revs;
init_revisions(&revs, NULL);
- init_pathspec(&revs.prune_data, opt->pathspec.raw);
+ copy_pathspec(&revs.prune_data, &opt->pathspec);
revs.diffopt = *opt;
if (diff_cache(&revs, tree_sha1, NULL, 1))
{
int i, prefixlen;
int no_index = 0;
- unsigned options = 0;
+ unsigned deprecated_show_diff_q_option_used = 0;
const char *paths[2];
/* Were we asked to do --no-index explicitly? */
path_inside_repo(prefix, argv[i+1])))
return;
}
- if (argc != i + 2)
+ if (argc != i + 2) {
+ if (!no_index) {
+ /*
+ * There was no --no-index and there were not two
+ * paths. It is possible that the user intended
+ * to do an inside-repository operation.
+ */
+ fprintf(stderr, "Not a git repository\n");
+ fprintf(stderr,
+ "To compare two paths outside a working tree:\n");
+ }
+ /* Give the usage message for non-repository usage and exit. */
usagef("git diff %s <path> <path>",
no_index ? "--no-index" : "[--no-index]");
+ }
diff_setup(&revs->diffopt);
for (i = 1; i < argc - 2; ) {
if (!strcmp(argv[i], "--no-index"))
i++;
else if (!strcmp(argv[i], "-q")) {
- options |= DIFF_SILENT_ON_REMOVED;
+ deprecated_show_diff_q_option_used = 1;
i++;
}
else if (!strcmp(argv[i], "--"))
revs->max_count = -2;
diff_setup_done(&revs->diffopt);
+ if (deprecated_show_diff_q_option_used)
+ handle_deprecated_show_diff_q(&revs->diffopt);
+
setup_diff_pager(&revs->diffopt);
DIFF_OPT_SET(&revs->diffopt, EXIT_WITH_STATUS);
return 1;
}
+static const char diff_status_letters[] = {
+ DIFF_STATUS_ADDED,
+ DIFF_STATUS_COPIED,
+ DIFF_STATUS_DELETED,
+ DIFF_STATUS_MODIFIED,
+ DIFF_STATUS_RENAMED,
+ DIFF_STATUS_TYPE_CHANGED,
+ DIFF_STATUS_UNKNOWN,
+ DIFF_STATUS_UNMERGED,
+ DIFF_STATUS_FILTER_AON,
+ DIFF_STATUS_FILTER_BROKEN,
+ '\0',
+};
+
+static unsigned int filter_bit['Z' + 1];
+
+static void prepare_filter_bits(void)
+{
+ int i;
+
+ if (!filter_bit[DIFF_STATUS_ADDED]) {
+ for (i = 0; diff_status_letters[i]; i++)
+ filter_bit[(int) diff_status_letters[i]] = (1 << i);
+ }
+}
+
+static unsigned filter_bit_tst(char status, const struct diff_options *opt)
+{
+ return opt->filter & filter_bit[(int) status];
+}
+
+static int parse_diff_filter_opt(const char *optarg, struct diff_options *opt)
+{
+ int i, optch;
+
+ prepare_filter_bits();
+
+ /*
+ * If there is a negation e.g. 'd' in the input, and we haven't
+ * initialized the filter field with another --diff-filter, start
+ * from full set of bits, except for AON.
+ */
+ if (!opt->filter) {
+ for (i = 0; (optch = optarg[i]) != '\0'; i++) {
+ if (optch < 'a' || 'z' < optch)
+ continue;
+ opt->filter = (1 << (ARRAY_SIZE(diff_status_letters) - 1)) - 1;
+ opt->filter &= ~filter_bit[DIFF_STATUS_FILTER_AON];
+ break;
+ }
+ }
+
+ for (i = 0; (optch = optarg[i]) != '\0'; i++) {
+ unsigned int bit;
+ int negate;
+
+ if ('a' <= optch && optch <= 'z') {
+ negate = 1;
+ optch = toupper(optch);
+ } else {
+ negate = 0;
+ }
+
+ bit = (0 <= optch && optch <= 'Z') ? filter_bit[optch] : 0;
+ if (!bit)
+ return optarg[i];
+ if (negate)
+ opt->filter &= ~bit;
+ else
+ opt->filter |= bit;
+ }
+ return 0;
+}
+
+/* Used only by "diff-files" and "diff --no-index" */
+void handle_deprecated_show_diff_q(struct diff_options *opt)
+{
+ warning("'diff -q' and 'diff-files -q' are deprecated.");
+ warning("Use 'diff --diff-filter=d' instead to ignore deleted filepairs.");
+ parse_diff_filter_opt("d", opt);
+}
+
static void enable_patch_output(int *fmt) {
*fmt &= ~DIFF_FORMAT_NO_OUTPUT;
*fmt |= DIFF_FORMAT_PATCH;
return argcount;
}
else if ((argcount = parse_long_opt("diff-filter", av, &optarg))) {
- options->filter = optarg;
+ int offending = parse_diff_filter_opt(optarg, options);
+ if (offending)
+ die("unknown change class '%c' in --diff-filter=%s",
+ offending, optarg);
return argcount;
}
else if (!strcmp(arg, "--abbrev"))
}
}
-static void diffcore_apply_filter(const char *filter)
+static int match_filter(const struct diff_options *options, const struct diff_filepair *p)
+{
+ return (((p->status == DIFF_STATUS_MODIFIED) &&
+ ((p->score &&
+ filter_bit_tst(DIFF_STATUS_FILTER_BROKEN, options)) ||
+ (!p->score &&
+ filter_bit_tst(DIFF_STATUS_MODIFIED, options)))) ||
+ ((p->status != DIFF_STATUS_MODIFIED) &&
+ filter_bit_tst(p->status, options)));
+}
+
+static void diffcore_apply_filter(struct diff_options *options)
{
int i;
struct diff_queue_struct *q = &diff_queued_diff;
struct diff_queue_struct outq;
+
DIFF_QUEUE_CLEAR(&outq);
- if (!filter)
+ if (!options->filter)
return;
- if (strchr(filter, DIFF_STATUS_FILTER_AON)) {
+ if (filter_bit_tst(DIFF_STATUS_FILTER_AON, options)) {
int found;
for (i = found = 0; !found && i < q->nr; i++) {
- struct diff_filepair *p = q->queue[i];
- if (((p->status == DIFF_STATUS_MODIFIED) &&
- ((p->score &&
- strchr(filter, DIFF_STATUS_FILTER_BROKEN)) ||
- (!p->score &&
- strchr(filter, DIFF_STATUS_MODIFIED)))) ||
- ((p->status != DIFF_STATUS_MODIFIED) &&
- strchr(filter, p->status)))
+ if (match_filter(options, q->queue[i]))
found++;
}
if (found)
/* Only the matching ones */
for (i = 0; i < q->nr; i++) {
struct diff_filepair *p = q->queue[i];
-
- if (((p->status == DIFF_STATUS_MODIFIED) &&
- ((p->score &&
- strchr(filter, DIFF_STATUS_FILTER_BROKEN)) ||
- (!p->score &&
- strchr(filter, DIFF_STATUS_MODIFIED)))) ||
- ((p->status != DIFF_STATUS_MODIFIED) &&
- strchr(filter, p->status)))
+ if (match_filter(options, p))
diff_q(&outq, p);
else
diff_free_filepair(p);
if (!options->found_follow)
/* See try_to_follow_renames() in tree-diff.c */
diff_resolve_rename_copy();
- diffcore_apply_filter(options->filter);
+ diffcore_apply_filter(options);
if (diff_queued_diff.nr && !DIFF_OPT_TST(options, DIFF_FROM_CONTENTS))
DIFF_OPT_SET(options, HAS_CHANGES);
#define DIFF_H
#include "tree-walk.h"
+#include "pathspec.h"
struct rev_info;
struct diff_options;
};
struct diff_options {
- const char *filter;
const char *orderfile;
const char *pickaxe;
const char *single_follow;
const char *a_prefix, *b_prefix;
unsigned flags;
+
+ /* diff-filter bits */
+ unsigned int filter;
+
int use_color;
int context;
int interhunkcontext;
extern const char mime_boundary_leader[];
-extern void diff_tree_setup_paths(const char **paths, struct diff_options *);
-extern void diff_tree_release_paths(struct diff_options *);
extern int diff_tree(struct tree_desc *t1, struct tree_desc *t2,
const char *base, struct diff_options *opt);
extern int diff_tree_sha1(const unsigned char *old, const unsigned char *new,
extern long parse_algorithm_value(const char *value);
+extern void handle_deprecated_show_diff_q(struct diff_options *);
+
extern int print_stat_summary(FILE *fp, int files,
int insertions, int deletions);
extern void setup_diff_pager(struct diff_options *);
#include "dir.h"
#include "refs.h"
#include "wildmatch.h"
+#include "pathspec.h"
struct path_simplify {
int len;
return fnmatch(pattern, string, flags | (ignore_case ? FNM_CASEFOLD : 0));
}
-inline int git_fnmatch(const char *pattern, const char *string,
- int flags, int prefix)
+inline int git_fnmatch(const struct pathspec_item *item,
+ const char *pattern, const char *string,
+ int prefix)
{
- int fnm_flags = 0;
- if (flags & GFNM_PATHNAME)
- fnm_flags |= FNM_PATHNAME;
if (prefix > 0) {
- if (strncmp(pattern, string, prefix))
+ if (ps_strncmp(item, pattern, string, prefix))
return FNM_NOMATCH;
pattern += prefix;
string += prefix;
}
- if (flags & GFNM_ONESTAR) {
+ if (item->flags & PATHSPEC_ONESTAR) {
int pattern_len = strlen(++pattern);
int string_len = strlen(string);
return string_len < pattern_len ||
- strcmp(pattern,
- string + string_len - pattern_len);
+ ps_strcmp(item, pattern,
+ string + string_len - pattern_len);
}
- return fnmatch(pattern, string, fnm_flags);
+ if (item->magic & PATHSPEC_GLOB)
+ return wildmatch(pattern, string,
+ WM_PATHNAME |
+ (item->magic & PATHSPEC_ICASE ? WM_CASEFOLD : 0),
+ NULL);
+ else
+ /* wildmatch has not learned no FNM_PATHNAME mode yet */
+ return fnmatch(pattern, string,
+ item->magic & PATHSPEC_ICASE ? FNM_CASEFOLD : 0);
}
static int fnmatch_icase_mem(const char *pattern, int patternlen,
return match_status;
}
-static size_t common_prefix_len(const char **pathspec)
+static size_t common_prefix_len(const struct pathspec *pathspec)
{
- const char *n, *first;
+ int n;
size_t max = 0;
- int literal = limit_pathspec_to_literal();
- if (!pathspec)
- return max;
-
- first = *pathspec;
- while ((n = *pathspec++)) {
- size_t i, len = 0;
- for (i = 0; first == n || i < max; i++) {
- char c = n[i];
- if (!c || c != first[i] || (!literal && is_glob_special(c)))
+ /*
+ * ":(icase)path" is treated as a pathspec full of
+ * wildcard. In other words, only prefix is considered common
+ * prefix. If the pathspec is abc/foo abc/bar, running in
+ * subdir xyz, the common prefix is still xyz, not xuz/abc as
+ * in non-:(icase).
+ */
+ GUARD_PATHSPEC(pathspec,
+ PATHSPEC_FROMTOP |
+ PATHSPEC_MAXDEPTH |
+ PATHSPEC_LITERAL |
+ PATHSPEC_GLOB |
+ PATHSPEC_ICASE);
+
+ for (n = 0; n < pathspec->nr; n++) {
+ size_t i = 0, len = 0, item_len;
+ if (pathspec->items[n].magic & PATHSPEC_ICASE)
+ item_len = pathspec->items[n].prefix;
+ else
+ item_len = pathspec->items[n].nowildcard_len;
+ while (i < item_len && (n == 0 || i < max)) {
+ char c = pathspec->items[n].match[i];
+ if (c != pathspec->items[0].match[i])
break;
if (c == '/')
len = i + 1;
+ i++;
}
- if (first == n || len < max) {
+ if (n == 0 || len < max) {
max = len;
if (!max)
break;
* Returns a copy of the longest leading path common among all
* pathspecs.
*/
-char *common_prefix(const char **pathspec)
+char *common_prefix(const struct pathspec *pathspec)
{
unsigned long len = common_prefix_len(pathspec);
- return len ? xmemdupz(*pathspec, len) : NULL;
+ return len ? xmemdupz(pathspec->items[0].match, len) : NULL;
}
-int fill_directory(struct dir_struct *dir, const char **pathspec)
+int fill_directory(struct dir_struct *dir, const struct pathspec *pathspec)
{
size_t len;
len = common_prefix_len(pathspec);
/* Read the directory and prune it */
- read_directory(dir, pathspec ? *pathspec : "", len, pathspec);
+ read_directory(dir, pathspec->nr ? pathspec->_raw[0] : "", len, pathspec);
return len;
}
return 1;
}
-/*
- * Does 'match' match the given name?
- * A match is found if
- *
- * (1) the 'match' string is leading directory of 'name', or
- * (2) the 'match' string is a wildcard and matches 'name', or
- * (3) the 'match' string is exactly the same as 'name'.
- *
- * and the return value tells which case it was.
- *
- * It returns 0 when there is no match.
- */
-static int match_one(const char *match, const char *name, int namelen)
-{
- int matchlen;
- int literal = limit_pathspec_to_literal();
-
- /* If the match was just the prefix, we matched */
- if (!*match)
- return MATCHED_RECURSIVELY;
-
- if (ignore_case) {
- for (;;) {
- unsigned char c1 = tolower(*match);
- unsigned char c2 = tolower(*name);
- if (c1 == '\0' || (!literal && is_glob_special(c1)))
- break;
- if (c1 != c2)
- return 0;
- match++;
- name++;
- namelen--;
- }
- } else {
- for (;;) {
- unsigned char c1 = *match;
- unsigned char c2 = *name;
- if (c1 == '\0' || (!literal && is_glob_special(c1)))
- break;
- if (c1 != c2)
- return 0;
- match++;
- name++;
- namelen--;
- }
- }
-
- /*
- * If we don't match the matchstring exactly,
- * we need to match by fnmatch
- */
- matchlen = strlen(match);
- if (strncmp_icase(match, name, matchlen)) {
- if (literal)
- return 0;
- return !fnmatch_icase(match, name, 0) ? MATCHED_FNMATCH : 0;
- }
-
- if (namelen == matchlen)
- return MATCHED_EXACTLY;
- if (match[matchlen-1] == '/' || name[matchlen] == '/')
- return MATCHED_RECURSIVELY;
- return 0;
-}
-
-/*
- * Given a name and a list of pathspecs, returns the nature of the
- * closest (i.e. most specific) match of the name to any of the
- * pathspecs.
- *
- * The caller typically calls this multiple times with the same
- * pathspec and seen[] array but with different name/namelen
- * (e.g. entries from the index) and is interested in seeing if and
- * how each pathspec matches all the names it calls this function
- * with. A mark is left in the seen[] array for each pathspec element
- * indicating the closest type of match that element achieved, so if
- * seen[n] remains zero after multiple invocations, that means the nth
- * pathspec did not match any names, which could indicate that the
- * user mistyped the nth pathspec.
- */
-int match_pathspec(const char **pathspec, const char *name, int namelen,
- int prefix, char *seen)
-{
- int i, retval = 0;
-
- if (!pathspec)
- return 1;
-
- name += prefix;
- namelen -= prefix;
-
- for (i = 0; pathspec[i] != NULL; i++) {
- int how;
- const char *match = pathspec[i] + prefix;
- if (seen && seen[i] == MATCHED_EXACTLY)
- continue;
- how = match_one(match, name, namelen);
- if (how) {
- if (retval < how)
- retval = how;
- if (seen && seen[i] < how)
- seen[i] = how;
- }
- }
- return retval;
-}
-
/*
* Does 'match' match the given name?
* A match is found if
const char *match = item->match + prefix;
int matchlen = item->len - prefix;
+ /*
+ * The normal call pattern is:
+ * 1. prefix = common_prefix_len(ps);
+ * 2. prune something, or fill_directory
+ * 3. match_pathspec_depth()
+ *
+ * 'prefix' at #1 may be shorter than the command's prefix and
+ * it's ok for #2 to match extra files. Those extras will be
+ * trimmed at #3.
+ *
+ * Suppose the pathspec is 'foo' and '../bar' running from
+ * subdir 'xyz'. The common prefix at #1 will be empty, thanks
+ * to "../". We may have xyz/foo _and_ XYZ/foo after #2. The
+ * user does not want XYZ/foo, only the "foo" part should be
+ * case-insensitive. We need to filter out XYZ/foo here. In
+ * other words, we do not trust the caller on comparing the
+ * prefix part when :(icase) is involved. We do exact
+ * comparison ourselves.
+ *
+ * Normally the caller (common_prefix_len() in fact) does
+ * _exact_ matching on name[-prefix+1..-1] and we do not need
+ * to check that part. Be defensive and check it anyway, in
+ * case common_prefix_len is changed, or a new caller is
+ * introduced that does not use common_prefix_len.
+ *
+ * If the penalty turns out too high when prefix is really
+ * long, maybe change it to
+ * strncmp(match, name, item->prefix - prefix)
+ */
+ if (item->prefix && (item->magic & PATHSPEC_ICASE) &&
+ strncmp(item->match, name - prefix, item->prefix))
+ return 0;
+
/* If the match was just the prefix, we matched */
if (!*match)
return MATCHED_RECURSIVELY;
- if (matchlen <= namelen && !strncmp(match, name, matchlen)) {
+ if (matchlen <= namelen && !ps_strncmp(item, match, name, matchlen)) {
if (matchlen == namelen)
return MATCHED_EXACTLY;
}
if (item->nowildcard_len < item->len &&
- !git_fnmatch(match, name,
- item->flags & PATHSPEC_ONESTAR ? GFNM_ONESTAR : 0,
+ !git_fnmatch(item, match, name,
item->nowildcard_len - prefix))
return MATCHED_FNMATCH;
{
int i, retval = 0;
+ GUARD_PATHSPEC(ps,
+ PATHSPEC_FROMTOP |
+ PATHSPEC_MAXDEPTH |
+ PATHSPEC_LITERAL |
+ PATHSPEC_GLOB |
+ PATHSPEC_ICASE);
+
if (!ps->nr) {
- if (!ps->recursive || ps->max_depth == -1)
+ if (!ps->recursive ||
+ !(ps->magic & PATHSPEC_MAXDEPTH) ||
+ ps->max_depth == -1)
return MATCHED_RECURSIVELY;
if (within_depth(name, namelen, 0, ps->max_depth))
if (seen && seen[i] == MATCHED_EXACTLY)
continue;
how = match_pathspec_item(ps->items+i, prefix, name, namelen);
- if (ps->recursive && ps->max_depth != -1 &&
+ if (ps->recursive &&
+ (ps->magic & PATHSPEC_MAXDEPTH) &&
+ ps->max_depth != -1 &&
how && how != MATCHED_FNMATCH) {
int len = ps->items[i].len;
if (name[len] == '/')
/*
* Return the length of the "simple" part of a path match limiter.
*/
-static int simple_length(const char *match)
+int simple_length(const char *match)
{
int len = -1;
}
}
-static int no_wildcard(const char *string)
+int no_wildcard(const char *string)
{
return string[simple_length(string)] == '\0';
}
unsigned long sz;
enum object_type type;
void *data;
- struct index_state *istate = &the_index;
len = strlen(path);
- pos = index_name_pos(istate, path, len);
+ pos = cache_name_pos(path, len);
if (pos < 0)
return NULL;
- if (!ce_skip_worktree(istate->cache[pos]))
+ if (!ce_skip_worktree(active_cache[pos]))
return NULL;
- data = read_sha1_file(istate->cache[pos]->sha1, &type, &sz);
+ data = read_sha1_file(active_cache[pos]->sha1, &type, &sz);
if (!data || type != OBJ_BLOB) {
free(data);
return NULL;
};
/*
- * Do not use the alphabetically stored index to look up
+ * Do not use the alphabetically sorted index to look up
* the directory name; instead, use the case insensitive
* name hash.
*/
static enum exist_status directory_exists_in_index_icase(const char *dirname, int len)
{
- const struct cache_entry *ce = index_name_exists(&the_index, dirname, len + 1, ignore_case);
+ const struct cache_entry *ce = cache_name_exists(dirname, len + 1, ignore_case);
unsigned char endchar;
if (!ce)
int dtype, struct dirent *de)
{
int exclude;
+ int has_path_in_index = !!cache_name_exists(path->buf, path->len, ignore_case);
+
if (dtype == DT_UNKNOWN)
dtype = get_dtype(de, path->buf, path->len);
/* Always exclude indexed files */
- if (dtype != DT_DIR &&
- cache_name_exists(path->buf, path->len, ignore_case))
+ if (dtype != DT_DIR && has_path_in_index)
return path_none;
+ /*
+ * When we are looking at a directory P in the working tree,
+ * there are three cases:
+ *
+ * (1) P exists in the index. Everything inside the directory P in
+ * the working tree needs to go when P is checked out from the
+ * index.
+ *
+ * (2) P does not exist in the index, but there is P/Q in the index.
+ * We know P will stay a directory when we check out the contents
+ * of the index, but we do not know yet if there is a directory
+ * P/Q in the working tree to be killed, so we need to recurse.
+ *
+ * (3) P does not exist in the index, and there is no P/Q in the index
+ * to require P to be a directory, either. Only in this case, we
+ * know that everything inside P will not be killed without
+ * recursing.
+ */
+ if ((dir->flags & DIR_COLLECT_KILLED_ONLY) &&
+ (dtype == DT_DIR) &&
+ !has_path_in_index) {
+ /*
+ * NEEDSWORK: directory_exists_in_index_icase()
+ * assumes that one byte past the given path is
+ * readable and has '/', which needs to be fixed, but
+ * until then, work it around in the caller.
+ */
+ strbuf_addch(path, '/');
+ if (directory_exists_in_index(path->buf, path->len - 1) ==
+ index_nonexistent) {
+ strbuf_setlen(path, path->len - 1);
+ return path_none;
+ }
+ strbuf_setlen(path, path->len - 1);
+ }
+
exclude = is_excluded(dir, path->buf, &dtype);
/*
return rc;
}
-int read_directory(struct dir_struct *dir, const char *path, int len, const char **pathspec)
+int read_directory(struct dir_struct *dir, const char *path, int len, const struct pathspec *pathspec)
{
struct path_simplify *simplify;
+ /*
+ * Check out create_simplify()
+ */
+ if (pathspec)
+ GUARD_PATHSPEC(pathspec,
+ PATHSPEC_FROMTOP |
+ PATHSPEC_MAXDEPTH |
+ PATHSPEC_LITERAL |
+ PATHSPEC_GLOB |
+ PATHSPEC_ICASE);
+
if (has_symlink_leading_path(path, len))
return dir->nr;
- simplify = create_simplify(pathspec);
+ simplify = create_simplify(pathspec ? pathspec->_raw : NULL);
if (!len || treat_leading_path(dir, path, len, simplify))
read_directory_recursive(dir, path, len, 0, simplify);
free_simplify(simplify);
return 0;
}
-static int pathspec_item_cmp(const void *a_, const void *b_)
-{
- struct pathspec_item *a, *b;
-
- a = (struct pathspec_item *)a_;
- b = (struct pathspec_item *)b_;
- return strcmp(a->match, b->match);
-}
-
-int init_pathspec(struct pathspec *pathspec, const char **paths)
-{
- const char **p = paths;
- int i;
-
- memset(pathspec, 0, sizeof(*pathspec));
- if (!p)
- return 0;
- while (*p)
- p++;
- pathspec->raw = paths;
- pathspec->nr = p - paths;
- if (!pathspec->nr)
- return 0;
-
- pathspec->items = xmalloc(sizeof(struct pathspec_item)*pathspec->nr);
- for (i = 0; i < pathspec->nr; i++) {
- struct pathspec_item *item = pathspec->items+i;
- const char *path = paths[i];
-
- item->match = path;
- item->len = strlen(path);
- item->flags = 0;
- if (limit_pathspec_to_literal()) {
- item->nowildcard_len = item->len;
- } else {
- item->nowildcard_len = simple_length(path);
- if (item->nowildcard_len < item->len) {
- pathspec->has_wildcard = 1;
- if (path[item->nowildcard_len] == '*' &&
- no_wildcard(path + item->nowildcard_len + 1))
- item->flags |= PATHSPEC_ONESTAR;
- }
- }
- }
-
- qsort(pathspec->items, pathspec->nr,
- sizeof(struct pathspec_item), pathspec_item_cmp);
-
- return 0;
-}
-
-void free_pathspec(struct pathspec *pathspec)
-{
- free(pathspec->items);
- pathspec->items = NULL;
-}
-
-int limit_pathspec_to_literal(void)
-{
- static int flag = -1;
- if (flag < 0)
- flag = git_env_bool(GIT_LITERAL_PATHSPECS_ENVIRONMENT, 0);
- return flag;
-}
-
/*
* Frees memory within dir which was allocated for exclude lists and
* the exclude_stack. Does not free dir itself.
DIR_HIDE_EMPTY_DIRECTORIES = 1<<2,
DIR_NO_GITLINKS = 1<<3,
DIR_COLLECT_IGNORED = 1<<4,
- DIR_SHOW_IGNORED_TOO = 1<<5
+ DIR_SHOW_IGNORED_TOO = 1<<5,
+ DIR_COLLECT_KILLED_ONLY = 1<<6
} flags;
struct dir_entry **entries;
struct dir_entry **ignored;
#define MATCHED_RECURSIVELY 1
#define MATCHED_FNMATCH 2
#define MATCHED_EXACTLY 3
-extern char *common_prefix(const char **pathspec);
-extern int match_pathspec(const char **pathspec, const char *name, int namelen, int prefix, char *seen);
+extern int simple_length(const char *match);
+extern int no_wildcard(const char *string);
+extern char *common_prefix(const struct pathspec *pathspec);
extern int match_pathspec_depth(const struct pathspec *pathspec,
const char *name, int namelen,
int prefix, char *seen);
extern int within_depth(const char *name, int namelen, int depth, int max_depth);
-extern int fill_directory(struct dir_struct *dir, const char **pathspec);
-extern int read_directory(struct dir_struct *, const char *path, int len, const char **pathspec);
+extern int fill_directory(struct dir_struct *dir, const struct pathspec *pathspec);
+extern int read_directory(struct dir_struct *, const char *path, int len, const struct pathspec *pathspec);
extern int is_excluded_from_list(const char *pathname, int pathlen, const char *basename,
int *dtype, struct exclude_list *el);
/*
* The prefix part of pattern must not contains wildcards.
*/
-#define GFNM_PATHNAME 1 /* similar to FNM_PATHNAME */
-#define GFNM_ONESTAR 2 /* there is only _one_ wildcard, a star */
-
-extern int git_fnmatch(const char *pattern, const char *string,
- int flags, int prefix);
+struct pathspec_item;
+extern int git_fnmatch(const struct pathspec_item *item,
+ const char *pattern, const char *string,
+ int prefix);
#endif
static void setup_git_env(void)
{
+ const char *gitfile;
+
git_dir = getenv(GIT_DIR_ENVIRONMENT);
- git_dir = git_dir ? xstrdup(git_dir) : NULL;
- if (!git_dir) {
- git_dir = read_gitfile(DEFAULT_GIT_DIR_ENVIRONMENT);
- git_dir = git_dir ? xstrdup(git_dir) : NULL;
- }
if (!git_dir)
git_dir = DEFAULT_GIT_DIR_ENVIRONMENT;
+ gitfile = read_gitfile(git_dir);
+ git_dir = xstrdup(gitfile ? gitfile : git_dir);
git_object_dir = getenv(DB_ENVIRONMENT);
if (!git_object_dir) {
git_object_dir = xmalloc(strlen(git_dir) + 9);
('author' (sp name)? sp '<' email '>' sp when lf)?
'committer' (sp name)? sp '<' email '>' sp when lf
commit_msg
- ('from' sp committish lf)?
- ('merge' sp committish lf)*
+ ('from' sp commit-ish lf)?
+ ('merge' sp commit-ish lf)*
(file_change | ls)*
lf?;
commit_msg ::= data;
file_obm ::= 'M' sp mode sp (hexsha1 | idnum) sp path_str lf;
file_inm ::= 'M' sp mode sp 'inline' sp path_str lf
data;
- note_obm ::= 'N' sp (hexsha1 | idnum) sp committish lf;
- note_inm ::= 'N' sp 'inline' sp committish lf
+ note_obm ::= 'N' sp (hexsha1 | idnum) sp commit-ish lf;
+ note_inm ::= 'N' sp 'inline' sp commit-ish lf
data;
new_tag ::= 'tag' sp tag_str lf
- 'from' sp committish lf
+ 'from' sp commit-ish lf
('tagger' (sp name)? sp '<' email '>' sp when lf)?
tag_msg;
tag_msg ::= data;
reset_branch ::= 'reset' sp ref_str lf
- ('from' sp committish lf)?
+ ('from' sp commit-ish lf)?
lf?;
checkpoint ::= 'checkpoint' lf
# stream formatting is: \, " and LF. Otherwise these values
# are UTF8.
#
- committish ::= (ref_str | hexsha1 | sha1exp_str | idnum);
+ commit-ish ::= (ref_str | hexsha1 | sha1exp_str | idnum);
ref_str ::= ref;
sha1exp_str ::= sha1exp;
tag_str ::= tag;
return 0;
if (read_ref(b->name, old_sha1))
hashclr(old_sha1);
- lock = lock_any_ref_for_update(b->name, old_sha1, 0);
+ lock = lock_any_ref_for_update(b->name, old_sha1, 0, NULL);
if (!lock)
return error("Unable to lock %s", b->name);
if (!force_update && !is_null_sha1(old_sha1)) {
assert(*p == ' ');
p++; /* skip space */
- /* <committish> */
+ /* <commit-ish> */
s = lookup_branch(p);
if (s) {
if (is_null_sha1(s->sha1))
case OBJ_TAG:
break;
default:
- die("Not a treeish: %s", command_buf.buf);
+ die("Not a tree-ish: %s", command_buf.buf);
}
if (oe->pack_id != MAX_PACK_ID) { /* in a pack being written */
struct tree_entry *root = NULL;
struct tree_entry leaf = {NULL};
- /* ls SP (<treeish> SP)? <path> */
+ /* ls SP (<tree-ish> SP)? <path> */
p = command_buf.buf + strlen("ls ");
if (*p == '"') {
if (!b)
#include "fetch-pack.h"
#include "remote.h"
#include "run-command.h"
+#include "connect.h"
#include "transport.h"
#include "version.h"
#include "prio-queue.h"
}
}
-struct write_shallow_data {
- struct strbuf *out;
- int use_pack_protocol;
- int count;
-};
-
-static int write_one_shallow(const struct commit_graft *graft, void *cb_data)
-{
- struct write_shallow_data *data = cb_data;
- const char *hex = sha1_to_hex(graft->sha1);
- data->count++;
- if (data->use_pack_protocol)
- packet_buf_write(data->out, "shallow %s", hex);
- else {
- strbuf_addstr(data->out, hex);
- strbuf_addch(data->out, '\n');
- }
- return 0;
-}
-
-static int write_shallow_commits(struct strbuf *out, int use_pack_protocol)
-{
- struct write_shallow_data data;
- data.out = out;
- data.use_pack_protocol = use_pack_protocol;
- data.count = 0;
- for_each_commit_graft(write_one_shallow, &data);
- return data.count;
-}
-
static enum ack_type get_ack(int fd, unsigned char *result_sha1)
{
int len;
return strcmp(a->name, b->name);
}
-static void setup_alternate_shallow(void)
-{
- struct strbuf sb = STRBUF_INIT;
- int fd;
-
- check_shallow_file_for_update();
- fd = hold_lock_file_for_update(&shallow_lock, git_path("shallow"),
- LOCK_DIE_ON_ERROR);
- if (write_shallow_commits(&sb, 0)) {
- if (write_in_full(fd, sb.buf, sb.len) != sb.len)
- die_errno("failed to write to %s", shallow_lock.filename);
- alternate_shallow_file = shallow_lock.filename;
- } else
- /*
- * is_repository_shallow() sees empty string as "no
- * shallow file".
- */
- alternate_shallow_file = "";
- strbuf_release(&sb);
-}
-
static struct ref *do_fetch_pack(struct fetch_pack_args *args,
int fd[2],
const struct ref *orig_ref,
if (args->stateless_rpc)
packet_flush(fd[1]);
if (args->depth > 0)
- setup_alternate_shallow();
+ setup_alternate_shallow(&shallow_lock, &alternate_shallow_file);
else
alternate_shallow_file = NULL;
if (get_pack(args, fd, pack_lockfile))
#define FETCH_PACK_H
#include "string-list.h"
+#include "run-command.h"
struct fetch_pack_args {
const char *uploadpack;
my %patch_mode_flavour = %{$patch_modes{stage}};
sub run_cmd_pipe {
- if ($^O eq 'MSWin32' || $^O eq 'msys') {
+ if ($^O eq 'MSWin32') {
my @invalid = grep {m/[":*]/} @_;
die "$^O does not support: @invalid\n" if @invalid;
my @args = map { m/ /o ? "\"$_\"": $_ } @_;
#define probe_utf8_pathname_composition(a,b)
#endif
-#ifdef NEEDS_CLIPPED_WRITE
-ssize_t clipped_write(int fildes, const void *buf, size_t nbyte);
-#define write(x,y,z) clipped_write((x),(y),(z))
-#endif
-
#ifdef MKDIR_WO_TRAILING_SLASH
#define mkdir(a,b) compat_mkdir_wo_trailing_slash((a),(b))
extern int compat_mkdir_wo_trailing_slash(const char*, mode_t);
# this half-converted form, but it isn't currently worth the
# backwards compatibility headaches.
- $mode=~/^\d\d(\d)\d{3}$/;
+ $mode=~/^\d{3}(\d)\d\d$/;
my $userBits=$1;
my $dbMode = "";
=head2 getRevisionDirMap
A "revision dir map" contains all the plain-file filenames associated
-with a particular revision (treeish), organized by directory:
+with a particular revision (tree-ish), organized by directory:
$type = $out->{$dir}{$fullName}
case "$filter_subdir" in
"")
- git read-tree -i -m $commit
+ GIT_ALLOW_NULL_SHA1=1 git read-tree -i -m $commit
;;
*)
# The commit may not have the subdirectory at all
- err=$(git read-tree -i -m $commit:"$filter_subdir" 2>&1) || {
+ err=$(GIT_ALLOW_NULL_SHA1=1 \
+ git read-tree -i -m $commit:"$filter_subdir" 2>&1) || {
if ! git rev-parse -q --verify $commit:"$filter_subdir"
then
rm -f "$GIT_INDEX_FILE"
# dictionary of all client parameters
entry = specList[0]
+ # the //client/ name
+ client_name = entry["Client"]
+
# just the keys that start with "View"
view_keys = [ k for k in entry.keys() if k.startswith("View") ]
# hold this new View
- view = View()
+ view = View(client_name)
# append the lines, in order, to the view
for view_num in range(len(view_keys)):
for b in body:
labelTemplate += "\t" + b + "\n"
labelTemplate += "View:\n"
- for mapping in clientSpec.mappings:
- labelTemplate += "\t%s\n" % mapping.depot_side.path
+ for depot_side in clientSpec.mappings:
+ labelTemplate += "\t%s\n" % depot_side
if self.dry_run:
print "Would create p4 label %s for tag" % name
# Use the label
p4_system(["tag", "-l", name] +
- ["%s@%s" % (mapping.depot_side.path, changelist) for mapping in clientSpec.mappings])
+ ["%s@%s" % (depot_side, changelist) for depot_side in clientSpec.mappings])
if verbose:
print "created p4 label for tag %s" % name
"""Represent a p4 view ("p4 help views"), and map files in a
repo according to the view."""
- class Path(object):
- """A depot or client path, possibly containing wildcards.
- The only one supported is ... at the end, currently.
- Initialize with the full path, with //depot or //client."""
-
- def __init__(self, path, is_depot):
- self.path = path
- self.is_depot = is_depot
- self.find_wildcards()
- # remember the prefix bit, useful for relative mappings
- m = re.match("(//[^/]+/)", self.path)
- if not m:
- die("Path %s does not start with //prefix/" % self.path)
- prefix = m.group(1)
- if not self.is_depot:
- # strip //client/ on client paths
- self.path = self.path[len(prefix):]
-
- def find_wildcards(self):
- """Make sure wildcards are valid, and set up internal
- variables."""
-
- self.ends_triple_dot = False
- # There are three wildcards allowed in p4 views
- # (see "p4 help views"). This code knows how to
- # handle "..." (only at the end), but cannot deal with
- # "%%n" or "*". Only check the depot_side, as p4 should
- # validate that the client_side matches too.
- if re.search(r'%%[1-9]', self.path):
- die("Can't handle %%n wildcards in view: %s" % self.path)
- if self.path.find("*") >= 0:
- die("Can't handle * wildcards in view: %s" % self.path)
- triple_dot_index = self.path.find("...")
- if triple_dot_index >= 0:
- if triple_dot_index != len(self.path) - 3:
- die("Can handle only single ... wildcard, at end: %s" %
- self.path)
- self.ends_triple_dot = True
-
- def ensure_compatible(self, other_path):
- """Make sure the wildcards agree."""
- if self.ends_triple_dot != other_path.ends_triple_dot:
- die("Both paths must end with ... if either does;\n" +
- "paths: %s %s" % (self.path, other_path.path))
-
- def match_wildcards(self, test_path):
- """See if this test_path matches us, and fill in the value
- of the wildcards if so. Returns a tuple of
- (True|False, wildcards[]). For now, only the ... at end
- is supported, so at most one wildcard."""
- if self.ends_triple_dot:
- dotless = self.path[:-3]
- if test_path.startswith(dotless):
- wildcard = test_path[len(dotless):]
- return (True, [ wildcard ])
- else:
- if test_path == self.path:
- return (True, [])
- return (False, [])
-
- def match(self, test_path):
- """Just return if it matches; don't bother with the wildcards."""
- b, _ = self.match_wildcards(test_path)
- return b
-
- def fill_in_wildcards(self, wildcards):
- """Return the relative path, with the wildcards filled in
- if there are any."""
- if self.ends_triple_dot:
- return self.path[:-3] + wildcards[0]
- else:
- return self.path
-
- class Mapping(object):
- def __init__(self, depot_side, client_side, overlay, exclude):
- # depot_side is without the trailing /... if it had one
- self.depot_side = View.Path(depot_side, is_depot=True)
- self.client_side = View.Path(client_side, is_depot=False)
- self.overlay = overlay # started with "+"
- self.exclude = exclude # started with "-"
- assert not (self.overlay and self.exclude)
- self.depot_side.ensure_compatible(self.client_side)
-
- def __str__(self):
- c = " "
- if self.overlay:
- c = "+"
- if self.exclude:
- c = "-"
- return "View.Mapping: %s%s -> %s" % \
- (c, self.depot_side.path, self.client_side.path)
-
- def map_depot_to_client(self, depot_path):
- """Calculate the client path if using this mapping on the
- given depot path; does not consider the effect of other
- mappings in a view. Even excluded mappings are returned."""
- matches, wildcards = self.depot_side.match_wildcards(depot_path)
- if not matches:
- return ""
- client_path = self.client_side.fill_in_wildcards(wildcards)
- return client_path
-
- #
- # View methods
- #
- def __init__(self):
+ def __init__(self, client_name):
self.mappings = []
+ self.client_prefix = "//%s/" % client_name
+ # cache results of "p4 where" to lookup client file locations
+ 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."""
+ 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.
depot_side = view_line[0:space_index]
rhs_index = space_index + 1
- if view_line[rhs_index] == '"':
- # Second word is double quoted. Make sure there is a
- # double quote at the end too.
- if not view_line.endswith('"'):
- die("View line with rhs quote should end with one: %s" %
- view_line)
- # skip the quotes
- client_side = view_line[rhs_index+1:-1]
- else:
- client_side = view_line[rhs_index:]
-
# prefix + means overlay on previous mapping
- overlay = False
if depot_side.startswith("+"):
- overlay = True
depot_side = depot_side[1:]
- # prefix - means exclude this path
+ # prefix - means exclude this path, leave out of mappings
exclude = False
if depot_side.startswith("-"):
exclude = True
depot_side = depot_side[1:]
- m = View.Mapping(depot_side, client_side, overlay, exclude)
- self.mappings.append(m)
+ if not exclude:
+ self.mappings.append(depot_side)
- 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."""
+ def convert_client_path(self, clientFile):
+ # chop off //client/ part to make it relative
+ if not clientFile.startswith(self.client_prefix):
+ die("No prefix '%s' on clientFile '%s'" %
+ (self.client_prefix, clientFile))
+ return clientFile[len(self.client_prefix):]
- paths_filled = []
- client_path = ""
+ def update_client_spec_path_cache(self, files):
+ """ Caching file paths by "p4 where" batch query """
- # look at later entries first
- for m in self.mappings[::-1]:
+ # List depot file paths exclude that already cached
+ fileArgs = [f['path'] for f in files if f['path'] not in self.client_spec_path_cache]
- # see where will this path end up in the client
- p = m.map_depot_to_client(depot_path)
+ if len(fileArgs) == 0:
+ return # All files in cache
- if p == "":
- # Depot path does not belong in client. Must remember
- # this, as previous items should not cause files to
- # exist in this path either. Remember that the list is
- # being walked from the end, which has higher precedence.
- # Overlap mappings do not exclude previous mappings.
- if not m.overlay:
- paths_filled.append(m.client_side)
+ where_result = p4CmdList(["-x", "-", "where"], stdin=fileArgs)
+ for res in where_result:
+ if "code" in res and res["code"] == "error":
+ # assume error is "... file(s) not in client view"
+ continue
+ if "clientFile" not in res:
+ die("No clientFile from 'p4 where %s'" % depot_path)
+ if "unmap" in res:
+ # it will list all of them, but only one not unmap-ped
+ continue
+ self.client_spec_path_cache[res['depotFile']] = self.convert_client_path(res["clientFile"])
- else:
- # This mapping matched; no need to search any further.
- # But, the mapping could be rejected if the client path
- # has already been claimed by an earlier mapping (i.e.
- # one later in the list, which we are walking backwards).
- already_mapped_in_client = False
- for f in paths_filled:
- # this is View.Path.match
- if f.match(p):
- already_mapped_in_client = True
- break
- if not already_mapped_in_client:
- # Include this file, unless it is from a line that
- # explicitly said to exclude it.
- if not m.exclude:
- client_path = p
+ # not found files or unmap files set to ""
+ for depotFile in fileArgs:
+ if depotFile not in self.client_spec_path_cache:
+ self.client_spec_path_cache[depotFile] = ""
- # a match, even if rejected, always stops the search
- break
+ 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 client_path
+ 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 )
+ return ""
class P4Sync(Command, P4UserMap):
delete_actions = ( "delete", "move/delete", "purge" )
"""Look at each depotFile in the commit to figure out to what
branch it belongs."""
+ if self.clientSpecDirs:
+ files = self.extractFilesFromCommit(commit)
+ self.clientSpecDirs.update_client_spec_path_cache(files)
+
branches = {}
fnum = 0
while commit.has_key("depotFile%s" % fnum):
else:
sys.stderr.write("Ignoring file outside of prefix: %s\n" % f['path'])
+ if self.clientSpecDirs:
+ self.clientSpecDirs.update_client_spec_path_cache(files)
+
self.gitStream.write("commit %s\n" % branch)
# gitStream.write("mark :%s\n" % details["change"])
self.committedChanges.add(int(details["change"]))
#
# Fetch one or more remote refs and merge it/them into the current HEAD.
-USAGE='[-n | --no-stat] [--[no-]commit] [--[no-]squash] [--[no-]ff] [-s strategy]... [<fetch-options>] <repo> <head>...'
+USAGE='[-n | --no-stat] [--[no-]commit] [--[no-]squash] [--[no-]ff] [--[no-]rebase|--rebase=preserve] [-s strategy]... [<fetch-options>] <repo> <head>...'
LONG_USAGE='Fetch one or more remote refs and integrate it/them with the current HEAD.'
SUBDIRECTORY_OK=Yes
OPTIONS_SPEC=
test -z "$(git ls-files -u)" || die_conflict
test -f "$GIT_DIR/MERGE_HEAD" && die_merge
+bool_or_string_config () {
+ git config --bool "$1" 2>/dev/null || git config "$1"
+}
+
strategy_args= diffstat= no_commit= squash= no_ff= ff_only=
log_arg= verbosity= progress= recurse_submodules= verify_signatures=
-merge_args= edit=
+merge_args= edit= rebase_args=
curr_branch=$(git symbolic-ref -q HEAD)
curr_branch_short="${curr_branch#refs/heads/}"
-rebase=$(git config --bool branch.$curr_branch_short.rebase)
+rebase=$(bool_or_string_config branch.$curr_branch_short.rebase)
if test -z "$rebase"
then
- rebase=$(git config --bool pull.rebase)
+ rebase=$(bool_or_string_config pull.rebase)
fi
dry_run=
while :
esac
merge_args="$merge_args$xx "
;;
+ -r=*|--r=*|--re=*|--reb=*|--reba=*|--rebas=*|--rebase=*)
+ rebase="${1#*=}"
+ ;;
-r|--r|--re|--reb|--reba|--rebas|--rebase)
rebase=true
;;
shift
done
+case "$rebase" in
+preserve)
+ rebase=true
+ rebase_args=--preserve-merges
+ ;;
+true|false|'')
+ ;;
+*)
+ echo "Invalid value for --rebase, should be true, false, or preserve"
+ usage
+ exit 1
+ ;;
+esac
+
error_on_no_merge_candidates () {
exec >&2
for opt
op_prep=with
fi
- curr_branch=${curr_branch#refs/heads/}
- upstream=$(git config "branch.$curr_branch.merge")
- remote=$(git config "branch.$curr_branch.remote")
+ upstream=$(git config "branch.$curr_branch_short.merge")
+ remote=$(git config "branch.$curr_branch_short.remote")
if [ $# -gt 1 ]; then
if [ "$rebase" = true ]; then
merge_name=$(git fmt-merge-msg $log_arg <"$GIT_DIR/FETCH_HEAD") || exit
case "$rebase" in
true)
- eval="git-rebase $diffstat $strategy_args $merge_args $verbosity"
+ eval="git-rebase $diffstat $strategy_args $merge_args $rebase_args $verbosity"
eval="$eval --onto $merge_head ${oldremoteref:-$merge_head}"
;;
*)
msg_content="$(commit_message $sha1)"
# No point in merging the first parent, that's HEAD
new_parents=${new_parents# $first_parent}
+ merge_args="--no-log --no-ff"
if ! do_with_author output eval \
- 'git merge --no-ff $strategy_args -m "$msg_content" $new_parents'
+ 'git merge $merge_args $strategy_args -m "$msg_content" $new_parents'
then
printf "%s\n" "$msg_content" > "$GIT_DIR"/MERGE_MSG
die_with_patch $sha1 "Error redoing merge $sha1"
;;
esac
;;
- 3,#*|3,)
+ 3,"$comment_char"*|3,)
# copy comments
;;
*)
die "Could not skip unnecessary pick commands"
}
+transform_todo_ids () {
+ while read -r command rest
+ do
+ case "$command" in
+ "$comment_char"* | exec)
+ # Be careful for oddball commands like 'exec'
+ # that do not have a SHA-1 at the beginning of $rest.
+ ;;
+ *)
+ sha1=$(git rev-parse --verify --quiet "$@" ${rest%% *}) &&
+ rest="$sha1 ${rest#* }"
+ ;;
+ esac
+ printf '%s\n' "$command${rest:+ }$rest"
+ done <"$todo" >"$todo.new" &&
+ mv -f "$todo.new" "$todo"
+}
+
+expand_todo_ids() {
+ transform_todo_ids
+}
+
+collapse_todo_ids() {
+ transform_todo_ids --short=7
+}
+
# Rearrange the todo list that has both "pick sha1 msg" and
# "pick sha1 fixup!/squash! msg" appears in it so that the latter
# comes immediately after the former, and change "pick" to
edit-todo)
git stripspace --strip-comments <"$todo" >"$todo".new
mv -f "$todo".new "$todo"
+ collapse_todo_ids
append_todo_help
git stripspace --comment-lines >>"$todo" <<\EOF
git_sequence_editor "$todo" ||
die "Could not execute editor"
+ expand_todo_ids
exit
;;
has_action "$todo" ||
die_abort "Nothing to do"
+expand_todo_ids
+
test -d "$rewritten" || test -n "$force_rebase" || skip_unnecessary_picks
GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $onto_name"
rm -rf "$state_dir"
}
-run_specific_rebase () {
+run_specific_rebase_internal () {
if [ "$interactive_rebase" = implied ]; then
GIT_EDITOR=:
export GIT_EDITOR
autosquash=
fi
+ # On FreeBSD, the shell's "return" returns from the current
+ # function, not from the current file inclusion.
+ # run_specific_rebase_internal has the file inclusion as a
+ # last statement, so POSIX and FreeBSD's return will do the
+ # same thing.
. git-rebase--$type
+}
+
+run_specific_rebase () {
+ run_specific_rebase_internal
ret=$?
if test $ret -eq 0
then
echo "*export-marks $gitmarks"
fi
test -n "$GIT_REMOTE_TESTGIT_SIGNED_TAGS" && echo "signed-tags"
+ test -n "$GIT_REMOTE_TESTGIT_NO_PRIVATE_UPDATE" && echo "no-private-update"
echo
;;
list)
+++ /dev/null
-#!/usr/bin/env python
-
-# This command is a simple remote-helper, that is used both as a
-# testcase for the remote-helper functionality, and as an example to
-# show remote-helper authors one possible implementation.
-#
-# This is a Git <-> Git importer/exporter, that simply uses git
-# fast-import and git fast-export to consume and produce fast-import
-# streams.
-#
-# To understand better the way things work, one can activate debug
-# traces by setting (to any value) the environment variables
-# GIT_TRANSPORT_HELPER_DEBUG and GIT_DEBUG_TESTGIT, to see messages
-# from the transport-helper side, or from this example remote-helper.
-
-# hashlib is only available in python >= 2.5
-try:
- import hashlib
- _digest = hashlib.sha1
-except ImportError:
- import sha
- _digest = sha.new
-import sys
-import os
-import time
-sys.path.insert(0, os.getenv("GITPYTHONLIB","."))
-
-from git_remote_helpers.util import die, debug, warn
-from git_remote_helpers.git.repo import GitRepo
-from git_remote_helpers.git.exporter import GitExporter
-from git_remote_helpers.git.importer import GitImporter
-from git_remote_helpers.git.non_local import NonLocalGit
-
-if sys.hexversion < 0x02000000:
- # string.encode() is the limiter
- sys.stderr.write("git-remote-testgit: requires Python 2.0 or later.\n")
- sys.exit(1)
-
-
-def encode_filepath(path):
- """Encodes a Unicode file path to a byte string.
-
- On Python 2 this is a no-op; on Python 3 we encode the string as
- suggested by [1] which allows an exact round-trip from the command line
- to the filesystem.
-
- [1] http://docs.python.org/3/c-api/unicode.html#file-system-encoding
-
- """
- if sys.hexversion < 0x03000000:
- return path
- return path.encode(sys.getfilesystemencoding(), 'surrogateescape')
-
-
-def get_repo(alias, url):
- """Returns a git repository object initialized for usage.
- """
-
- repo = GitRepo(url)
- repo.get_revs()
- repo.get_head()
-
- hasher = _digest()
- hasher.update(encode_filepath(repo.path))
- repo.hash = hasher.hexdigest()
-
- repo.get_base_path = lambda base: os.path.join(
- base, 'info', 'fast-import', repo.hash)
-
- prefix = 'refs/testgit/%s/' % alias
- debug("prefix: '%s'", prefix)
-
- repo.gitdir = os.environ["GIT_DIR"]
- repo.alias = alias
- repo.prefix = prefix
-
- repo.exporter = GitExporter(repo)
- repo.importer = GitImporter(repo)
- repo.non_local = NonLocalGit(repo)
-
- return repo
-
-
-def local_repo(repo, path):
- """Returns a git repository object initalized for usage.
- """
-
- local = GitRepo(path)
-
- local.non_local = None
- local.gitdir = repo.gitdir
- local.alias = repo.alias
- local.prefix = repo.prefix
- local.hash = repo.hash
- local.get_base_path = repo.get_base_path
- local.exporter = GitExporter(local)
- local.importer = GitImporter(local)
-
- return local
-
-
-def do_capabilities(repo, args):
- """Prints the supported capabilities.
- """
-
- print("import")
- print("export")
- print("refspec refs/heads/*:%s*" % repo.prefix)
-
- dirname = repo.get_base_path(repo.gitdir)
-
- if not os.path.exists(dirname):
- os.makedirs(dirname)
-
- path = os.path.join(dirname, 'git.marks')
-
- print("*export-marks %s" % path)
- if os.path.exists(path):
- print("*import-marks %s" % path)
-
- print('') # end capabilities
-
-
-def do_list(repo, args):
- """Lists all known references.
-
- Bug: This will always set the remote head to master for non-local
- repositories, since we have no way of determining what the remote
- head is at clone time.
- """
-
- for ref in repo.revs:
- debug("? refs/heads/%s", ref)
- print("? refs/heads/%s" % ref)
-
- if repo.head:
- debug("@refs/heads/%s HEAD" % repo.head)
- print("@refs/heads/%s HEAD" % repo.head)
- else:
- debug("@refs/heads/master HEAD")
- print("@refs/heads/master HEAD")
-
- print('') # end list
-
-
-def update_local_repo(repo):
- """Updates (or clones) a local repo.
- """
-
- if repo.local:
- return repo
-
- path = repo.non_local.clone(repo.gitdir)
- repo.non_local.update(repo.gitdir)
- repo = local_repo(repo, path)
- return repo
-
-
-def do_import(repo, args):
- """Exports a fast-import stream from testgit for git to import.
- """
-
- if len(args) != 1:
- die("Import needs exactly one ref")
-
- if not repo.gitdir:
- die("Need gitdir to import")
-
- ref = args[0]
- refs = [ref]
-
- while True:
- line = sys.stdin.readline().decode()
- if line == '\n':
- break
- if not line.startswith('import '):
- die("Expected import line.")
-
- # strip of leading 'import '
- ref = line[7:].strip()
- refs.append(ref)
-
- print("feature done")
-
- if os.environ.get("GIT_REMOTE_TESTGIT_FAILURE"):
- die('Told to fail')
-
- repo = update_local_repo(repo)
- repo.exporter.export_repo(repo.gitdir, refs)
-
- print("done")
-
-
-def do_export(repo, args):
- """Imports a fast-import stream from git to testgit.
- """
-
- if not repo.gitdir:
- die("Need gitdir to export")
-
- if os.environ.get("GIT_REMOTE_TESTGIT_FAILURE"):
- die('Told to fail')
-
- update_local_repo(repo)
- changed = repo.importer.do_import(repo.gitdir)
-
- if not repo.local:
- repo.non_local.push(repo.gitdir)
-
- for ref in changed:
- print("ok %s" % ref)
- print('')
-
-
-COMMANDS = {
- 'capabilities': do_capabilities,
- 'list': do_list,
- 'import': do_import,
- 'export': do_export,
-}
-
-
-def sanitize(value):
- """Cleans up the url.
- """
-
- if value.startswith('testgit::'):
- value = value[9:]
-
- return value
-
-
-def read_one_line(repo):
- """Reads and processes one command.
- """
-
- sleepy = os.environ.get("GIT_REMOTE_TESTGIT_SLEEPY")
- if sleepy:
- debug("Sleeping %d sec before readline" % int(sleepy))
- time.sleep(int(sleepy))
-
- line = sys.stdin.readline()
-
- cmdline = line.decode()
-
- if not cmdline:
- warn("Unexpected EOF")
- return False
-
- cmdline = cmdline.strip().split()
- if not cmdline:
- # Blank line means we're about to quit
- return False
-
- cmd = cmdline.pop(0)
- debug("Got command '%s' with args '%s'", cmd, ' '.join(cmdline))
-
- if cmd not in COMMANDS:
- die("Unknown command, %s", cmd)
-
- func = COMMANDS[cmd]
- func(repo, cmdline)
- sys.stdout.flush()
-
- return True
-
-
-def main(args):
- """Starts a new remote helper for the specified repository.
- """
-
- if len(args) != 3:
- die("Expecting exactly three arguments.")
- sys.exit(1)
-
- if os.getenv("GIT_DEBUG_TESTGIT"):
- import git_remote_helpers.util
- git_remote_helpers.util.DEBUG = True
-
- alias = sanitize(args[1])
- url = sanitize(args[2])
-
- if not alias.isalnum():
- warn("non-alnum alias '%s'", alias)
- alias = "tmp"
-
- args[1] = alias
- args[2] = url
-
- repo = get_repo(alias, url)
-
- debug("Got arguments %s", args[1:])
-
- more = True
-
- # Use binary mode since Python 3 does not permit unbuffered I/O in text
- # mode. Unbuffered I/O is required to avoid data that should be going
- # to git-fast-import after an "export" command getting caught in our
- # stdin buffer instead.
- sys.stdin = os.fdopen(sys.stdin.fileno(), 'rb', 0)
- while (more):
- more = read_one_line(repo)
-
-if __name__ == '__main__':
- sys.exit(main(sys.argv))
if ($smtp->code == 220) {
$smtp = Net::SMTP::SSL->start_SSL($smtp,
ssl_verify_params())
- or die "STARTTLS failed! ".$smtp->message;
+ or die "STARTTLS failed! ".IO::Socket::SSL::errstr();
$smtp_encryption = '';
# Send EHLO again to receive fresh
# supported commands
echo
fi
echo
- done |
- if test -n "$for_status"; then
- if [ -n "$files" ]; then
- gettextln "Submodules changed but not updated:" | git stripspace -c
- else
- gettextln "Submodule changes to be committed:" | git stripspace -c
- fi
- printf "\n" | git stripspace -c
- git stripspace -c
- else
- cat
- fi
+ done
}
#
# List all submodules, prefixed with:
#include "commit.h"
const char git_usage_string[] =
- "git [--version] [--help] [-c name=value]\n"
+ "git [--version] [--help] [-C <path>] [-c name=value]\n"
" [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]\n"
" [-p|--paginate|--no-pager] [--no-replace-objects] [--bare]\n"
" [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]\n"
setenv(GIT_LITERAL_PATHSPECS_ENVIRONMENT, "0", 1);
if (envchanged)
*envchanged = 1;
+ } else if (!strcmp(cmd, "--glob-pathspecs")) {
+ setenv(GIT_GLOB_PATHSPECS_ENVIRONMENT, "1", 1);
+ if (envchanged)
+ *envchanged = 1;
+ } else if (!strcmp(cmd, "--noglob-pathspecs")) {
+ setenv(GIT_NOGLOB_PATHSPECS_ENVIRONMENT, "1", 1);
+ if (envchanged)
+ *envchanged = 1;
+ } else if (!strcmp(cmd, "--icase-pathspecs")) {
+ setenv(GIT_ICASE_PATHSPECS_ENVIRONMENT, "1", 1);
+ if (envchanged)
+ *envchanged = 1;
} else if (!strcmp(cmd, "--shallow-file")) {
(*argv)++;
(*argc)--;
set_alternate_shallow_file((*argv)[0]);
if (envchanged)
*envchanged = 1;
+ } else if (!strcmp(cmd, "-C")) {
+ if (*argc < 2) {
+ fprintf(stderr, "No directory given for -C.\n" );
+ usage(git_usage_string);
+ }
+ if (chdir((*argv)[1]))
+ die_errno("Cannot change to '%s'", (*argv)[1]);
+ if (envchanged)
+ *envchanged = 1;
+ (*argv)++;
+ (*argc)--;
} else {
fprintf(stderr, "Unknown option: %s\n", cmd);
usage(git_usage_string);
+++ /dev/null
-/GIT-PYTHON-VERSION
-/build
-/dist
+++ /dev/null
-#
-# Makefile for the git_remote_helpers python support modules
-#
-pysetupfile:=setup.py
-
-# Shell quote (do not use $(call) to accommodate ancient setups);
-DESTDIR_SQ = $(subst ','\'',$(DESTDIR))
-
-ifndef PYTHON_PATH
- ifeq ($(uname_S),FreeBSD)
- PYTHON_PATH = /usr/local/bin/python
- else
- PYTHON_PATH = /usr/bin/python
- endif
-endif
-ifndef prefix
- prefix = $(HOME)
-endif
-ifndef V
- QUIET = @
- QUIETSETUP = --quiet
-endif
-
-PYLIBDIR=$(shell $(PYTHON_PATH) -c \
- "import sys; \
- print('lib/python%i.%i/site-packages' % sys.version_info[:2])")
-
-py_version=$(shell $(PYTHON_PATH) -c \
- 'import sys; print("%i.%i" % sys.version_info[:2])')
-
-all: $(pysetupfile)
- $(QUIET)test "$$(cat GIT-PYTHON-VERSION 2>/dev/null)" = "$(py_version)" || \
- flags=--force; \
- $(PYTHON_PATH) $(pysetupfile) $(QUIETSETUP) build $$flags
- $(QUIET)echo "$(py_version)" >GIT-PYTHON-VERSION
-
-install: $(pysetupfile)
- $(PYTHON_PATH) $(pysetupfile) install --prefix $(DESTDIR_SQ)$(prefix)
-
-instlibdir: $(pysetupfile)
- @echo "$(DESTDIR_SQ)$(prefix)/$(PYLIBDIR)"
-
-clean:
- $(QUIET)$(PYTHON_PATH) $(pysetupfile) $(QUIETSETUP) clean -a
- $(RM) *.pyo *.pyc GIT-PYTHON-VERSION
+++ /dev/null
-#!/usr/bin/env python
-
-"""Support library package for git remote helpers.
-
-Git remote helpers are helper commands that interfaces with a non-git
-repository to provide automatic import of non-git history into a Git
-repository.
-
-This package provides the support library needed by these helpers..
-The following modules are included:
-
-- git.git - Interaction with Git repositories
-
-- util - General utility functionality use by the other modules in
- this package, and also used directly by the helpers.
-"""
+++ /dev/null
-import sys
-if sys.hexversion < 0x02040000:
- # The limiter is the subprocess module
- sys.stderr.write("git_remote_helpers: requires Python 2.4 or later.\n")
- sys.exit(1)
+++ /dev/null
-import os
-import subprocess
-import sys
-
-from git_remote_helpers.util import check_call
-
-
-class GitExporter(object):
- """An exporter for testgit repositories.
-
- The exporter simply delegates to git fast-export.
- """
-
- def __init__(self, repo):
- """Creates a new exporter for the specified repo.
- """
-
- self.repo = repo
-
- def export_repo(self, base, refs=None):
- """Exports a fast-export stream for the given directory.
-
- Simply delegates to git fast-epxort and pipes it through sed
- to make the refs show up under the prefix rather than the
- default refs/heads. This is to demonstrate how the export
- data can be stored under it's own ref (using the refspec
- capability).
-
- If None, refs defaults to ["HEAD"].
- """
-
- if not refs:
- refs = ["HEAD"]
-
- dirname = self.repo.get_base_path(base)
- path = os.path.abspath(os.path.join(dirname, 'testgit.marks'))
-
- if not os.path.exists(dirname):
- os.makedirs(dirname)
-
- print "feature relative-marks"
- if os.path.exists(os.path.join(dirname, 'git.marks')):
- print "feature import-marks=%s/git.marks" % self.repo.hash
- print "feature export-marks=%s/git.marks" % self.repo.hash
- sys.stdout.flush()
-
- args = ["git", "--git-dir=" + self.repo.gitpath, "fast-export", "--export-marks=" + path]
-
- if os.path.exists(path):
- args.append("--import-marks=" + path)
-
- args.extend(refs)
-
- p1 = subprocess.Popen(args, stdout=subprocess.PIPE)
-
- args = ["sed", "s_refs/heads/_" + self.repo.prefix + "_g"]
-
- check_call(args, stdin=p1.stdout)
+++ /dev/null
-#!/usr/bin/env python
-
-"""Functionality for interacting with Git repositories.
-
-This module provides classes for interfacing with a Git repository.
-"""
-
-import os
-import re
-import time
-from binascii import hexlify
-from cStringIO import StringIO
-import unittest
-
-from git_remote_helpers.util import debug, error, die, start_command, run_command
-
-
-def get_git_dir ():
- """Return the path to the GIT_DIR for this repo."""
- args = ("git", "rev-parse", "--git-dir")
- exit_code, output, errors = run_command(args)
- if exit_code:
- die("Failed to retrieve git dir")
- assert not errors
- return output.strip()
-
-
-def parse_git_config ():
- """Return a dict containing the parsed version of 'git config -l'."""
- exit_code, output, errors = run_command(("git", "config", "-z", "-l"))
- if exit_code:
- die("Failed to retrieve git configuration")
- assert not errors
- return dict([e.split('\n', 1) for e in output.split("\0") if e])
-
-
-def git_config_bool (value):
- """Convert the given git config string value to True or False.
-
- Raise ValueError if the given string was not recognized as a
- boolean value.
-
- """
- norm_value = str(value).strip().lower()
- if norm_value in ("true", "1", "yes", "on", ""):
- return True
- if norm_value in ("false", "0", "no", "off", "none"):
- return False
- raise ValueError("Failed to parse '%s' into a boolean value" % (value))
-
-
-def valid_git_ref (ref_name):
- """Return True iff the given ref name is a valid git ref name."""
- # The following is a reimplementation of the git check-ref-format
- # command. The rules were derived from the git check-ref-format(1)
- # manual page. This code should be replaced by a call to
- # check_refname_format() in the git library, when such is available.
- if ref_name.endswith('/') or \
- ref_name.startswith('.') or \
- ref_name.count('/.') or \
- ref_name.count('..') or \
- ref_name.endswith('.lock'):
- return False
- for c in ref_name:
- if ord(c) < 0x20 or ord(c) == 0x7f or c in " ~^:?*[":
- return False
- return True
-
-
-class GitObjectFetcher(object):
-
- """Provide parsed access to 'git cat-file --batch'.
-
- This provides a read-only interface to the Git object database.
-
- """
-
- def __init__ (self):
- """Initiate a 'git cat-file --batch' session."""
- self.queue = [] # List of object names to be submitted
- self.in_transit = None # Object name currently in transit
-
- # 'git cat-file --batch' produces binary output which is likely
- # to be corrupted by the default "rU"-mode pipe opened by
- # start_command. (Mode == "rU" does universal new-line
- # conversion, which mangles carriage returns.) Therefore, we
- # open an explicitly binary-safe pipe for transferring the
- # output from 'git cat-file --batch'.
- pipe_r_fd, pipe_w_fd = os.pipe()
- pipe_r = os.fdopen(pipe_r_fd, "rb")
- pipe_w = os.fdopen(pipe_w_fd, "wb")
- self.proc = start_command(("git", "cat-file", "--batch"),
- stdout = pipe_w)
- self.f = pipe_r
-
- def __del__ (self):
- """Verify completed communication with 'git cat-file --batch'."""
- assert not self.queue
- assert self.in_transit is None
- self.proc.stdin.close()
- assert self.proc.wait() == 0 # Zero exit code
- assert self.f.read() == "" # No remaining output
-
- def _submit_next_object (self):
- """Submit queue items to the 'git cat-file --batch' process.
-
- If there are items in the queue, and there is currently no item
- currently in 'transit', then pop the first item off the queue,
- and submit it.
-
- """
- if self.queue and self.in_transit is None:
- self.in_transit = self.queue.pop(0)
- print >> self.proc.stdin, self.in_transit[0]
-
- def push (self, obj, callback):
- """Push the given object name onto the queue.
-
- The given callback function will at some point in the future
- be called exactly once with the following arguments:
- - self - this GitObjectFetcher instance
- - obj - the object name provided to push()
- - sha1 - the SHA1 of the object, if 'None' obj is missing
- - t - the type of the object (tag/commit/tree/blob)
- - size - the size of the object in bytes
- - data - the object contents
-
- """
- self.queue.append((obj, callback))
- self._submit_next_object() # (Re)start queue processing
-
- def process_next_entry (self):
- """Read the next entry off the queue and invoke callback."""
- obj, cb = self.in_transit
- self.in_transit = None
- header = self.f.readline()
- if header == "%s missing\n" % (obj):
- cb(self, obj, None, None, None, None)
- return
- sha1, t, size = header.split(" ")
- assert len(sha1) == 40
- assert t in ("tag", "commit", "tree", "blob")
- assert size.endswith("\n")
- size = int(size.strip())
- data = self.f.read(size)
- assert self.f.read(1) == "\n"
- cb(self, obj, sha1, t, size, data)
- self._submit_next_object()
-
- def process (self):
- """Process the current queue until empty."""
- while self.in_transit is not None:
- self.process_next_entry()
-
- # High-level convenience methods:
-
- def get_sha1 (self, objspec):
- """Return the SHA1 of the object specified by 'objspec'.
-
- Return None if 'objspec' does not specify an existing object.
-
- """
- class _ObjHandler(object):
- """Helper class for getting the returned SHA1."""
- def __init__ (self, parser):
- self.parser = parser
- self.sha1 = None
-
- def __call__ (self, parser, obj, sha1, t, size, data):
- # FIXME: Many unused arguments. Could this be cheaper?
- assert parser == self.parser
- self.sha1 = sha1
-
- handler = _ObjHandler(self)
- self.push(objspec, handler)
- self.process()
- return handler.sha1
-
- def open_obj (self, objspec):
- """Return a file object wrapping the contents of a named object.
-
- The caller is responsible for calling .close() on the returned
- file object.
-
- Raise KeyError if 'objspec' does not exist in the repo.
-
- """
- class _ObjHandler(object):
- """Helper class for parsing the returned git object."""
- def __init__ (self, parser):
- """Set up helper."""
- self.parser = parser
- self.contents = StringIO()
- self.err = None
-
- def __call__ (self, parser, obj, sha1, t, size, data):
- """Git object callback (see GitObjectFetcher documentation)."""
- assert parser == self.parser
- if not sha1: # Missing object
- self.err = "Missing object '%s'" % obj
- else:
- assert size == len(data)
- self.contents.write(data)
-
- handler = _ObjHandler(self)
- self.push(objspec, handler)
- self.process()
- if handler.err:
- raise KeyError(handler.err)
- handler.contents.seek(0)
- return handler.contents
-
- def walk_tree (self, tree_objspec, callback, prefix = ""):
- """Recursively walk the given Git tree object.
-
- Recursively walk all subtrees of the given tree object, and
- invoke the given callback passing three arguments:
- (path, mode, data) with the path, permission bits, and contents
- of all the blobs found in the entire tree structure.
-
- """
- class _ObjHandler(object):
- """Helper class for walking a git tree structure."""
- def __init__ (self, parser, cb, path, mode = None):
- """Set up helper."""
- self.parser = parser
- self.cb = cb
- self.path = path
- self.mode = mode
- self.err = None
-
- def parse_tree (self, treedata):
- """Parse tree object data, yield tree entries.
-
- Each tree entry is a 3-tuple (mode, sha1, path)
-
- self.path is prepended to all paths yielded
- from this method.
-
- """
- while treedata:
- mode = int(treedata[:6], 10)
- # Turn 100xxx into xxx
- if mode > 100000:
- mode -= 100000
- assert treedata[6] == " "
- i = treedata.find("\0", 7)
- assert i > 0
- path = treedata[7:i]
- sha1 = hexlify(treedata[i + 1: i + 21])
- yield (mode, sha1, self.path + path)
- treedata = treedata[i + 21:]
-
- def __call__ (self, parser, obj, sha1, t, size, data):
- """Git object callback (see GitObjectFetcher documentation)."""
- assert parser == self.parser
- if not sha1: # Missing object
- self.err = "Missing object '%s'" % (obj)
- return
- assert size == len(data)
- if t == "tree":
- if self.path:
- self.path += "/"
- # Recurse into all blobs and subtrees
- for m, s, p in self.parse_tree(data):
- parser.push(s,
- self.__class__(self.parser, self.cb, p, m))
- elif t == "blob":
- self.cb(self.path, self.mode, data)
- else:
- raise ValueError("Unknown object type '%s'" % (t))
-
- self.push(tree_objspec, _ObjHandler(self, callback, prefix))
- self.process()
-
-
-class GitRefMap(object):
-
- """Map Git ref names to the Git object names they currently point to.
-
- Behaves like a dictionary of Git ref names -> Git object names.
-
- """
-
- def __init__ (self, obj_fetcher):
- """Create a new Git ref -> object map."""
- self.obj_fetcher = obj_fetcher
- self._cache = {} # dict: refname -> objname
-
- def _load (self, ref):
- """Retrieve the object currently bound to the given ref.
-
- The name of the object pointed to by the given ref is stored
- into this mapping, and also returned.
-
- """
- if ref not in self._cache:
- self._cache[ref] = self.obj_fetcher.get_sha1(ref)
- return self._cache[ref]
-
- def __contains__ (self, refname):
- """Return True if the given refname is present in this cache."""
- return bool(self._load(refname))
-
- def __getitem__ (self, refname):
- """Return the git object name pointed to by the given refname."""
- commit = self._load(refname)
- if commit is None:
- raise KeyError("Unknown ref '%s'" % (refname))
- return commit
-
- def get (self, refname, default = None):
- """Return the git object name pointed to by the given refname."""
- commit = self._load(refname)
- if commit is None:
- return default
- return commit
-
-
-class GitFICommit(object):
-
- """Encapsulate the data in a Git fast-import commit command."""
-
- SHA1RE = re.compile(r'^[0-9a-f]{40}$')
-
- @classmethod
- def parse_mode (cls, mode):
- """Verify the given git file mode, and return it as a string."""
- assert mode in (644, 755, 100644, 100755, 120000)
- return "%i" % (mode)
-
- @classmethod
- def parse_objname (cls, objname):
- """Return the given object name (or mark number) as a string."""
- if isinstance(objname, int): # Object name is a mark number
- assert objname > 0
- return ":%i" % (objname)
-
- # No existence check is done, only checks for valid format
- assert cls.SHA1RE.match(objname) # Object name is valid SHA1
- return objname
-
- @classmethod
- def quote_path (cls, path):
- """Return a quoted version of the given path."""
- path = path.replace("\\", "\\\\")
- path = path.replace("\n", "\\n")
- path = path.replace('"', '\\"')
- return '"%s"' % (path)
-
- @classmethod
- def parse_path (cls, path):
- """Verify that the given path is valid, and quote it, if needed."""
- assert not isinstance(path, int) # Cannot be a mark number
-
- # These checks verify the rules on the fast-import man page
- assert not path.count("//")
- assert not path.endswith("/")
- assert not path.startswith("/")
- assert not path.count("/./")
- assert not path.count("/../")
- assert not path.endswith("/.")
- assert not path.endswith("/..")
- assert not path.startswith("./")
- assert not path.startswith("../")
-
- if path.count('"') + path.count('\n') + path.count('\\'):
- return cls.quote_path(path)
- return path
-
- def __init__ (self, name, email, timestamp, timezone, message):
- """Create a new Git fast-import commit, with the given metadata."""
- self.name = name
- self.email = email
- self.timestamp = timestamp
- self.timezone = timezone
- self.message = message
- self.pathops = [] # List of path operations in this commit
-
- def modify (self, mode, blobname, path):
- """Add a file modification to this Git fast-import commit."""
- self.pathops.append(("M",
- self.parse_mode(mode),
- self.parse_objname(blobname),
- self.parse_path(path)))
-
- def delete (self, path):
- """Add a file deletion to this Git fast-import commit."""
- self.pathops.append(("D", self.parse_path(path)))
-
- def copy (self, path, newpath):
- """Add a file copy to this Git fast-import commit."""
- self.pathops.append(("C",
- self.parse_path(path),
- self.parse_path(newpath)))
-
- def rename (self, path, newpath):
- """Add a file rename to this Git fast-import commit."""
- self.pathops.append(("R",
- self.parse_path(path),
- self.parse_path(newpath)))
-
- def note (self, blobname, commit):
- """Add a note object to this Git fast-import commit."""
- self.pathops.append(("N",
- self.parse_objname(blobname),
- self.parse_objname(commit)))
-
- def deleteall (self):
- """Delete all files in this Git fast-import commit."""
- self.pathops.append("deleteall")
-
-
-class TestGitFICommit(unittest.TestCase):
-
- """GitFICommit selftests."""
-
- def test_basic (self):
- """GitFICommit basic selftests."""
-
- def expect_fail (method, data):
- """Verify that the method(data) raises an AssertionError."""
- try:
- method(data)
- except AssertionError:
- return
- raise AssertionError("Failed test for invalid data '%s(%s)'" %
- (method.__name__, repr(data)))
-
- def test_parse_mode (self):
- """GitFICommit.parse_mode() selftests."""
- self.assertEqual(GitFICommit.parse_mode(644), "644")
- self.assertEqual(GitFICommit.parse_mode(755), "755")
- self.assertEqual(GitFICommit.parse_mode(100644), "100644")
- self.assertEqual(GitFICommit.parse_mode(100755), "100755")
- self.assertEqual(GitFICommit.parse_mode(120000), "120000")
- self.assertRaises(AssertionError, GitFICommit.parse_mode, 0)
- self.assertRaises(AssertionError, GitFICommit.parse_mode, 123)
- self.assertRaises(AssertionError, GitFICommit.parse_mode, 600)
- self.assertRaises(AssertionError, GitFICommit.parse_mode, "644")
- self.assertRaises(AssertionError, GitFICommit.parse_mode, "abc")
-
- def test_parse_objname (self):
- """GitFICommit.parse_objname() selftests."""
- self.assertEqual(GitFICommit.parse_objname(1), ":1")
- self.assertRaises(AssertionError, GitFICommit.parse_objname, 0)
- self.assertRaises(AssertionError, GitFICommit.parse_objname, -1)
- self.assertEqual(GitFICommit.parse_objname("0123456789" * 4),
- "0123456789" * 4)
- self.assertEqual(GitFICommit.parse_objname("2468abcdef" * 4),
- "2468abcdef" * 4)
- self.assertRaises(AssertionError, GitFICommit.parse_objname,
- "abcdefghij" * 4)
-
- def test_parse_path (self):
- """GitFICommit.parse_path() selftests."""
- self.assertEqual(GitFICommit.parse_path("foo/bar"), "foo/bar")
- self.assertEqual(GitFICommit.parse_path("path/with\n and \" in it"),
- '"path/with\\n and \\" in it"')
- self.assertRaises(AssertionError, GitFICommit.parse_path, 1)
- self.assertRaises(AssertionError, GitFICommit.parse_path, 0)
- self.assertRaises(AssertionError, GitFICommit.parse_path, -1)
- self.assertRaises(AssertionError, GitFICommit.parse_path, "foo//bar")
- self.assertRaises(AssertionError, GitFICommit.parse_path, "foo/bar/")
- self.assertRaises(AssertionError, GitFICommit.parse_path, "/foo/bar")
- self.assertRaises(AssertionError, GitFICommit.parse_path, "foo/./bar")
- self.assertRaises(AssertionError, GitFICommit.parse_path, "foo/../bar")
- self.assertRaises(AssertionError, GitFICommit.parse_path, "foo/bar/.")
- self.assertRaises(AssertionError, GitFICommit.parse_path, "foo/bar/..")
- self.assertRaises(AssertionError, GitFICommit.parse_path, "./foo/bar")
- self.assertRaises(AssertionError, GitFICommit.parse_path, "../foo/bar")
-
-
-class GitFastImport(object):
-
- """Encapsulate communication with git fast-import."""
-
- def __init__ (self, f, obj_fetcher, last_mark = 0):
- """Set up self to communicate with a fast-import process through f."""
- self.f = f # File object where fast-import stream is written
- self.obj_fetcher = obj_fetcher # GitObjectFetcher instance
- self.next_mark = last_mark + 1 # Next mark number
- self.refs = set() # Keep track of the refnames we've seen
-
- def comment (self, s):
- """Write the given comment in the fast-import stream."""
- assert "\n" not in s, "Malformed comment: '%s'" % (s)
- self.f.write("# %s\n" % (s))
-
- def commit (self, ref, commitdata):
- """Make a commit on the given ref, with the given GitFICommit.
-
- Return the mark number identifying this commit.
-
- """
- self.f.write("""\
-commit %(ref)s
-mark :%(mark)i
-committer %(name)s <%(email)s> %(timestamp)i %(timezone)s
-data %(msgLength)i
-%(msg)s
-""" % {
- 'ref': ref,
- 'mark': self.next_mark,
- 'name': commitdata.name,
- 'email': commitdata.email,
- 'timestamp': commitdata.timestamp,
- 'timezone': commitdata.timezone,
- 'msgLength': len(commitdata.message),
- 'msg': commitdata.message,
-})
-
- if ref not in self.refs:
- self.refs.add(ref)
- parent = ref + "^0"
- if self.obj_fetcher.get_sha1(parent):
- self.f.write("from %s\n" % (parent))
-
- for op in commitdata.pathops:
- self.f.write(" ".join(op))
- self.f.write("\n")
- self.f.write("\n")
- retval = self.next_mark
- self.next_mark += 1
- return retval
-
- def blob (self, data):
- """Import the given blob.
-
- Return the mark number identifying this blob.
-
- """
- self.f.write("blob\nmark :%i\ndata %i\n%s\n" %
- (self.next_mark, len(data), data))
- retval = self.next_mark
- self.next_mark += 1
- return retval
-
- def reset (self, ref, objname):
- """Reset the given ref to point at the given Git object."""
- self.f.write("reset %s\nfrom %s\n\n" %
- (ref, GitFICommit.parse_objname(objname)))
- if ref not in self.refs:
- self.refs.add(ref)
-
-
-class GitNotes(object):
-
- """Encapsulate access to Git notes.
-
- Simulates a dictionary of object name (SHA1) -> Git note mappings.
-
- """
-
- def __init__ (self, notes_ref, obj_fetcher):
- """Create a new Git notes interface, bound to the given notes ref."""
- self.notes_ref = notes_ref
- self.obj_fetcher = obj_fetcher # Used to get objects from repo
- self.imports = [] # list: (objname, note data blob name) tuples
-
- def __del__ (self):
- """Verify that self.commit_notes() was called before destruction."""
- if self.imports:
- error("Missing call to self.commit_notes().")
- error("%i notes are not committed!", len(self.imports))
-
- def _load (self, objname):
- """Return the note data associated with the given git object.
-
- The note data is returned in string form. If no note is found
- for the given object, None is returned.
-
- """
- try:
- f = self.obj_fetcher.open_obj("%s:%s" % (self.notes_ref, objname))
- ret = f.read()
- f.close()
- except KeyError:
- ret = None
- return ret
-
- def __getitem__ (self, objname):
- """Return the note contents associated with the given object.
-
- Raise KeyError if given object has no associated note.
-
- """
- blobdata = self._load(objname)
- if blobdata is None:
- raise KeyError("Object '%s' has no note" % (objname))
- return blobdata
-
- def get (self, objname, default = None):
- """Return the note contents associated with the given object.
-
- Return given default if given object has no associated note.
-
- """
- blobdata = self._load(objname)
- if blobdata is None:
- return default
- return blobdata
-
- def import_note (self, objname, data, gfi):
- """Tell git fast-import to store data as a note for objname.
-
- This method uses the given GitFastImport object to create a
- blob containing the given note data. Also an entry mapping the
- given object name to the created blob is stored until
- commit_notes() is called.
-
- Note that this method only works if it is later followed by a
- call to self.commit_notes() (which produces the note commit
- that refers to the blob produced here).
-
- """
- if not data.endswith("\n"):
- data += "\n"
- gfi.comment("Importing note for object %s" % (objname))
- mark = gfi.blob(data)
- self.imports.append((objname, mark))
-
- def commit_notes (self, gfi, author, message):
- """Produce a git fast-import note commit for the imported notes.
-
- This method uses the given GitFastImport object to create a
- commit on the notes ref, introducing the notes previously
- submitted to import_note().
-
- """
- if not self.imports:
- return
- commitdata = GitFICommit(author[0], author[1],
- time.time(), "0000", message)
- for objname, blobname in self.imports:
- assert isinstance(objname, int) and objname > 0
- assert isinstance(blobname, int) and blobname > 0
- commitdata.note(blobname, objname)
- gfi.commit(self.notes_ref, commitdata)
- self.imports = []
-
-
-class GitCachedNotes(GitNotes):
-
- """Encapsulate access to Git notes (cached version).
-
- Only use this class if no caching is done at a higher level.
-
- Simulates a dictionary of object name (SHA1) -> Git note mappings.
-
- """
-
- def __init__ (self, notes_ref, obj_fetcher):
- """Set up a caching wrapper around GitNotes."""
- GitNotes.__init__(self, notes_ref, obj_fetcher)
- self._cache = {} # Cache: object name -> note data
-
- def __del__ (self):
- """Verify that GitNotes' destructor is called."""
- GitNotes.__del__(self)
-
- def _load (self, objname):
- """Extend GitNotes._load() with a local objname -> note cache."""
- if objname not in self._cache:
- self._cache[objname] = GitNotes._load(self, objname)
- return self._cache[objname]
-
- def import_note (self, objname, data, gfi):
- """Extend GitNotes.import_note() with a local objname -> note cache."""
- if not data.endswith("\n"):
- data += "\n"
- assert objname not in self._cache
- self._cache[objname] = data
- GitNotes.import_note(self, objname, data, gfi)
-
-
-if __name__ == '__main__':
- unittest.main()
+++ /dev/null
-import os
-import subprocess
-
-from git_remote_helpers.util import check_call, check_output
-
-
-class GitImporter(object):
- """An importer for testgit repositories.
-
- This importer simply delegates to git fast-import.
- """
-
- def __init__(self, repo):
- """Creates a new importer for the specified repo.
- """
-
- self.repo = repo
-
- def get_refs(self, gitdir):
- """Returns a dictionary with refs.
-
- Note that the keys in the returned dictionary are byte strings as
- read from git.
- """
- args = ["git", "--git-dir=" + gitdir, "for-each-ref", "refs/heads"]
- lines = check_output(args).strip().split('\n'.encode('ascii'))
- refs = {}
- for line in lines:
- value, name = line.split(' '.encode('ascii'))
- name = name.strip('commit\t'.encode('ascii'))
- refs[name] = value
- return refs
-
- def do_import(self, base):
- """Imports a fast-import stream to the given directory.
-
- Simply delegates to git fast-import.
- """
-
- dirname = self.repo.get_base_path(base)
- if self.repo.local:
- gitdir = self.repo.gitpath
- else:
- gitdir = os.path.abspath(os.path.join(dirname, '.git'))
- path = os.path.abspath(os.path.join(dirname, 'testgit.marks'))
-
- if not os.path.exists(dirname):
- os.makedirs(dirname)
-
- refs_before = self.get_refs(gitdir)
-
- args = ["git", "--git-dir=" + gitdir, "fast-import", "--quiet", "--export-marks=" + path]
-
- if os.path.exists(path):
- args.append("--import-marks=" + path)
-
- check_call(args)
-
- refs_after = self.get_refs(gitdir)
-
- changed = {}
-
- for name, value in refs_after.iteritems():
- if refs_before.get(name) == value:
- continue
-
- changed[name] = value
-
- return changed
+++ /dev/null
-import os
-import subprocess
-
-from git_remote_helpers.util import check_call, die, warn
-
-
-class NonLocalGit(object):
- """Handler to interact with non-local repos.
- """
-
- def __init__(self, repo):
- """Creates a new non-local handler for the specified repo.
- """
-
- self.repo = repo
-
- def clone(self, base):
- """Clones the non-local repo to base.
-
- Does nothing if a clone already exists.
- """
-
- path = os.path.join(self.repo.get_base_path(base), '.git')
-
- # already cloned
- if os.path.exists(path):
- return path
-
- os.makedirs(path)
- args = ["git", "clone", "--bare", "--quiet", self.repo.gitpath, path]
-
- check_call(args)
-
- return path
-
- def update(self, base):
- """Updates checkout of the non-local repo in base.
- """
-
- path = os.path.join(self.repo.get_base_path(base), '.git')
-
- if not os.path.exists(path):
- die("could not find repo at %s", path)
-
- args = ["git", "--git-dir=" + path, "fetch", "--quiet", self.repo.gitpath]
- check_call(args)
-
- args = ["git", "--git-dir=" + path, "update-ref", "refs/heads/master", "FETCH_HEAD"]
- child = check_call(args)
-
- def push(self, base):
- """Pushes from the non-local repo to base.
- """
-
- path = os.path.join(self.repo.get_base_path(base), '.git')
-
- if not os.path.exists(path):
- die("could not find repo at %s", path)
-
- args = ["git", "--git-dir=" + path, "push", "--quiet", self.repo.gitpath, "--all"]
- child = check_call(args)
+++ /dev/null
-import os
-import subprocess
-
-from git_remote_helpers.util import check_call
-
-
-def sanitize(rev, sep='\t'):
- """Converts a for-each-ref line to a name/value pair.
- """
-
- splitrev = rev.split(sep)
- branchval = splitrev[0]
- branchname = splitrev[1].strip()
- if branchname.startswith("refs/heads/"):
- branchname = branchname[11:]
-
- return branchname, branchval
-
-def is_remote(url):
- """Checks whether the specified value is a remote url.
- """
-
- prefixes = ["http", "file", "git"]
-
- for prefix in prefixes:
- if url.startswith(prefix):
- return True
- return False
-
-class GitRepo(object):
- """Repo object representing a repo.
- """
-
- def __init__(self, path):
- """Initializes a new repo at the given path.
- """
-
- self.path = path
- self.head = None
- self.revmap = {}
- self.local = not is_remote(self.path)
-
- if(self.path.endswith('.git')):
- self.gitpath = self.path
- else:
- self.gitpath = os.path.join(self.path, '.git')
-
- if self.local and not os.path.exists(self.gitpath):
- os.makedirs(self.gitpath)
-
- def get_revs(self):
- """Fetches all revs from the remote.
- """
-
- args = ["git", "ls-remote", self.gitpath]
- path = ".cached_revs"
- ofile = open(path, "w")
-
- check_call(args, stdout=ofile)
- output = open(path).readlines()
- self.revmap = dict(sanitize(i) for i in output)
- if "HEAD" in self.revmap:
- del self.revmap["HEAD"]
- self.revs = self.revmap.keys()
- ofile.close()
-
- def get_head(self):
- """Determines the head of a local repo.
- """
-
- if not self.local:
- return
-
- path = os.path.join(self.gitpath, "HEAD")
- head = open(path).readline()
- self.head, _ = sanitize(head, ' ')
+++ /dev/null
-[build]
-build_purelib = build/lib
-build_platlib = build/lib
+++ /dev/null
-#!/usr/bin/env python
-
-"""Distutils build/install script for the git_remote_helpers package."""
-
-from distutils.core import setup
-
-# If building under Python3 we need to run 2to3 on the code, do this by
-# trying to import distutils' 2to3 builder, which is only available in
-# Python3.
-try:
- from distutils.command.build_py import build_py_2to3 as build_py
-except ImportError:
- # 2.x
- from distutils.command.build_py import build_py
-
-setup(
- name = 'git_remote_helpers',
- version = '0.1.0',
- description = 'Git remote helper program for non-git repositories',
- license = 'GPLv2',
- author = 'The Git Community',
- author_email = 'git@vger.kernel.org',
- url = 'http://www.git-scm.com/',
- package_dir = {'git_remote_helpers': ''},
- packages = ['git_remote_helpers', 'git_remote_helpers.git'],
- cmdclass = {'build_py': build_py},
-)
+++ /dev/null
-#!/usr/bin/env python
-
-"""Misc. useful functionality used by the rest of this package.
-
-This module provides common functionality used by the other modules in
-this package.
-
-"""
-
-import sys
-import os
-import subprocess
-
-try:
- from subprocess import CalledProcessError
-except ImportError:
- # from python2.7:subprocess.py
- # Exception classes used by this module.
- class CalledProcessError(Exception):
- """This exception is raised when a process run by check_call() returns
- a non-zero exit status. The exit status will be stored in the
- returncode attribute."""
- def __init__(self, returncode, cmd):
- self.returncode = returncode
- self.cmd = cmd
- def __str__(self):
- return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode)
-
-
-# Whether or not to show debug messages
-DEBUG = False
-
-def notify(msg, *args):
- """Print a message to stderr."""
- print >> sys.stderr, msg % args
-
-def debug (msg, *args):
- """Print a debug message to stderr when DEBUG is enabled."""
- if DEBUG:
- print >> sys.stderr, msg % args
-
-def error (msg, *args):
- """Print an error message to stderr."""
- print >> sys.stderr, "ERROR:", msg % args
-
-def warn(msg, *args):
- """Print a warning message to stderr."""
- print >> sys.stderr, "warning:", msg % args
-
-def die (msg, *args):
- """Print as error message to stderr and exit the program."""
- error(msg, *args)
- sys.exit(1)
-
-
-class ProgressIndicator(object):
-
- """Simple progress indicator.
-
- Displayed as a spinning character by default, but can be customized
- by passing custom messages that overrides the spinning character.
-
- """
-
- States = ("|", "/", "-", "\\")
-
- def __init__ (self, prefix = "", f = sys.stdout):
- """Create a new ProgressIndicator, bound to the given file object."""
- self.n = 0 # Simple progress counter
- self.f = f # Progress is written to this file object
- self.prev_len = 0 # Length of previous msg (to be overwritten)
- self.prefix = prefix # Prefix prepended to each progress message
- self.prefix_lens = [] # Stack of prefix string lengths
-
- def pushprefix (self, prefix):
- """Append the given prefix onto the prefix stack."""
- self.prefix_lens.append(len(self.prefix))
- self.prefix += prefix
-
- def popprefix (self):
- """Remove the last prefix from the prefix stack."""
- prev_len = self.prefix_lens.pop()
- self.prefix = self.prefix[:prev_len]
-
- def __call__ (self, msg = None, lf = False):
- """Indicate progress, possibly with a custom message."""
- if msg is None:
- msg = self.States[self.n % len(self.States)]
- msg = self.prefix + msg
- print >> self.f, "\r%-*s" % (self.prev_len, msg),
- self.prev_len = len(msg.expandtabs())
- if lf:
- print >> self.f
- self.prev_len = 0
- self.n += 1
-
- def finish (self, msg = "done", noprefix = False):
- """Finalize progress indication with the given message."""
- if noprefix:
- self.prefix = ""
- self(msg, True)
-
-
-def start_command (args, cwd = None, shell = False, add_env = None,
- stdin = subprocess.PIPE, stdout = subprocess.PIPE,
- stderr = subprocess.PIPE):
- """Start the given command, and return a subprocess object.
-
- This provides a simpler interface to the subprocess module.
-
- """
- env = None
- if add_env is not None:
- env = os.environ.copy()
- env.update(add_env)
- return subprocess.Popen(args, bufsize = 1, stdin = stdin, stdout = stdout,
- stderr = stderr, cwd = cwd, shell = shell,
- env = env, universal_newlines = True)
-
-
-def run_command (args, cwd = None, shell = False, add_env = None,
- flag_error = True):
- """Run the given command to completion, and return its results.
-
- This provides a simpler interface to the subprocess module.
-
- The results are formatted as a 3-tuple: (exit_code, output, errors)
-
- If flag_error is enabled, Error messages will be produced if the
- subprocess terminated with a non-zero exit code and/or stderr
- output.
-
- The other arguments are passed on to start_command().
-
- """
- process = start_command(args, cwd, shell, add_env)
- (output, errors) = process.communicate()
- exit_code = process.returncode
- if flag_error and errors:
- error("'%s' returned errors:\n---\n%s---", " ".join(args), errors)
- if flag_error and exit_code:
- error("'%s' returned exit code %i", " ".join(args), exit_code)
- return (exit_code, output, errors)
-
-
-# from python2.7:subprocess.py
-def call(*popenargs, **kwargs):
- """Run command with arguments. Wait for command to complete, then
- return the returncode attribute.
-
- The arguments are the same as for the Popen constructor. Example:
-
- retcode = call(["ls", "-l"])
- """
- return subprocess.Popen(*popenargs, **kwargs).wait()
-
-
-# from python2.7:subprocess.py
-def check_call(*popenargs, **kwargs):
- """Run command with arguments. Wait for command to complete. If
- the exit code was zero then return, otherwise raise
- CalledProcessError. The CalledProcessError object will have the
- return code in the returncode attribute.
-
- The arguments are the same as for the Popen constructor. Example:
-
- check_call(["ls", "-l"])
- """
- retcode = call(*popenargs, **kwargs)
- if retcode:
- cmd = kwargs.get("args")
- if cmd is None:
- cmd = popenargs[0]
- raise CalledProcessError(retcode, cmd)
- return 0
-
-
-# from python2.7:subprocess.py
-def check_output(*popenargs, **kwargs):
- r"""Run command with arguments and return its output as a byte string.
-
- If the exit code was non-zero it raises a CalledProcessError. The
- CalledProcessError object will have the return code in the returncode
- attribute and output in the output attribute.
-
- The arguments are the same as for the Popen constructor. Example:
-
- >>> check_output(["ls", "-l", "/dev/null"])
- 'crw-rw-rw- 1 root root 1, 3 Oct 18 2007 /dev/null\n'
-
- The stdout argument is not allowed as it is used internally.
- To capture standard error in the result, use stderr=STDOUT.
-
- >>> check_output(["/bin/sh", "-c",
- ... "ls -l non_existent_file ; exit 0"],
- ... stderr=STDOUT)
- 'ls: non_existent_file: No such file or directory\n'
- """
- if 'stdout' in kwargs:
- raise ValueError('stdout argument not allowed, it will be overridden.')
- process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs)
- output, unused_err = process.communicate()
- retcode = process.poll()
- if retcode:
- cmd = kwargs.get("args")
- if cmd is None:
- cmd = popenargs[0]
- raise subprocess.CalledProcessError(retcode, cmd)
- return output
-
-
-def file_reader_method (missing_ok = False):
- """Decorator for simplifying reading of files.
-
- If missing_ok is True, a failure to open a file for reading will
- not raise the usual IOError, but instead the wrapped method will be
- called with f == None. The method must in this case properly
- handle f == None.
-
- """
- def _wrap (method):
- """Teach given method to handle both filenames and file objects.
-
- The given method must take a file object as its second argument
- (the first argument being 'self', of course). This decorator
- will take a filename given as the second argument and promote
- it to a file object.
-
- """
- def _wrapped_method (self, filename, *args, **kwargs):
- if isinstance(filename, file):
- f = filename
- else:
- try:
- f = open(filename, 'r')
- except IOError:
- if missing_ok:
- f = None
- else:
- raise
- try:
- return method(self, f, *args, **kwargs)
- finally:
- if not isinstance(filename, file) and f:
- f.close()
- return _wrapped_method
- return _wrap
-
-
-def file_writer_method (method):
- """Decorator for simplifying writing of files.
-
- Enables the given method to handle both filenames and file objects.
-
- The given method must take a file object as its second argument
- (the first argument being 'self', of course). This decorator will
- take a filename given as the second argument and promote it to a
- file object.
-
- """
- def _new_method (self, filename, *args, **kwargs):
- if isinstance(filename, file):
- f = filename
- else:
- # Make sure the containing directory exists
- parent_dir = os.path.dirname(filename)
- if not os.path.isdir(parent_dir):
- os.makedirs(parent_dir)
- f = open(filename, 'w')
- try:
- return method(self, f, *args, **kwargs)
- finally:
- if not isinstance(filename, file):
- f.close()
- return _new_method
$cgi->input({-name=>"h", -value=>$search_hash, -type=>"hidden"}) . "\n" .
$cgi->popup_menu(-name => 'st', -default => 'commit',
-values => ['commit', 'grep', 'author', 'committer', 'pickaxe']) .
- $cgi->sup($cgi->a({-href => href(action=>"search_help")}, "?")) .
- " search:\n",
+ " " . $cgi->a({-href => href(action=>"search_help"),
+ -title => "search help" }, "?") . " search:\n",
$cgi->textfield(-name => "s", -value => $searchtext, -override => 1) . "\n" .
"<span title=\"Extended regular expression\">" .
$cgi->checkbox(-name => 'sr', -value => 1, -label => 're',
print "<div class=\"title\"> </div>\n";
print "<table class=\"projects_list\">\n" .
"<tr id=\"metadata_desc\"><td>description</td><td>" . esc_html($descr) . "</td></tr>\n";
- unless ($omit_owner) {
+ if ($owner and not $omit_owner) {
print "<tr id=\"metadata_owner\"><td>owner</td><td>" . esc_html($owner) . "</td></tr>\n";
}
if (defined $cd{'rfc2822'}) {
$hash_base, '--', $file_name
or die_error(500, "Open git-blame --porcelain failed");
}
+ binmode $fd, ':utf8';
# incremental blame data returns early
if ($format eq 'data') {
}
div.page_footer {
- height: 17px;
+ height: 22px;
padding: 4px 8px;
background-color: #d9d8d1;
}
div.page_footer_text {
+ line-height: 22px;
float: left;
color: #555555;
font-style: italic;
a.rss_logo {
float: right;
- padding: 3px 0px;
- width: 35px;
+ padding: 3px 5px;
line-height: 10px;
border: 1px solid;
border-color: #fcc7a5 #7d3302 #3e1a01 #ff954e;
if (strcmp(method, c->method)) {
const char *proto = getenv("SERVER_PROTOCOL");
- if (proto && !strcmp(proto, "HTTP/1.1"))
+ if (proto && !strcmp(proto, "HTTP/1.1")) {
http_status(405, "Method Not Allowed");
- else
+ hdr_str("Allow", !strcmp(c->method, "GET") ?
+ "GET, HEAD" : c->method);
+ } else
http_status(400, "Bad Request");
hdr_nocache();
end_headers();
break;
}
- free(tree->buffer);
- tree->buffer = NULL;
+ free_tree_buffer(tree);
return p;
}
pushing = 0;
if (prepare_revision_walk(&revs))
die("revision walk setup failed");
- mark_edges_uninteresting(revs.commits, &revs, NULL);
+ mark_edges_uninteresting(&revs, NULL);
objects_to_send = get_delta(&revs, ref_lock);
finish_all_active_slots();
#include "sideband.h"
#include "run-command.h"
#include "url.h"
+#include "urlmatch.h"
#include "credential.h"
#include "version.h"
#include "pkt-line.h"
static int curl_ftp_no_epsv;
static const char *curl_http_proxy;
static const char *curl_cookie_file;
+static int curl_save_cookies;
static struct credential http_auth = CREDENTIAL_INIT;
static int http_proactive_auth;
static const char *user_agent;
if (!strcmp("http.sslcainfo", var))
return git_config_string(&ssl_cainfo, var, value);
if (!strcmp("http.sslcertpasswordprotected", var)) {
- if (git_config_bool(var, value))
- ssl_cert_password_required = 1;
+ ssl_cert_password_required = git_config_bool(var, value);
return 0;
}
if (!strcmp("http.ssltry", var)) {
if (!strcmp("http.cookiefile", var))
return git_config_string(&curl_cookie_file, var, value);
+ if (!strcmp("http.savecookies", var)) {
+ curl_save_cookies = git_config_bool(var, value);
+ return 0;
+ }
if (!strcmp("http.postbuffer", var)) {
http_post_buffer = git_config_int(var, value);
{
char *low_speed_limit;
char *low_speed_time;
+ char *normalized_url;
+ struct urlmatch_config config = { STRING_LIST_INIT_DUP };
+
+ config.section = "http";
+ config.key = NULL;
+ config.collect_fn = http_options;
+ config.cascade_fn = git_default_config;
+ config.cb = NULL;
http_is_verbose = 0;
+ normalized_url = url_normalize(url, &config.url);
- git_config(http_options, NULL);
+ git_config(urlmatch_config_entry, &config);
+ free(normalized_url);
curl_global_init(CURL_GLOBAL_ALL);
slot->callback_data = NULL;
slot->callback_func = NULL;
curl_easy_setopt(slot->curl, CURLOPT_COOKIEFILE, curl_cookie_file);
+ if (curl_save_cookies)
+ curl_easy_setopt(slot->curl, CURLOPT_COOKIEJAR, curl_cookie_file);
curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, pragma_header);
curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, curl_errorstr);
curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, NULL);
/* Either initialization would be fine */
#define RANGE_SET_INIT {0}
-static void range_set_init(struct range_set *rs, size_t prealloc)
+void range_set_init(struct range_set *rs, size_t prealloc)
{
rs->alloc = rs->nr = 0;
rs->ranges = NULL;
range_set_grow(rs, prealloc);
}
-static void range_set_release(struct range_set *rs)
+void range_set_release(struct range_set *rs)
{
free(rs->ranges);
rs->alloc = rs->nr = 0;
}
/* tack on a _new_ range _at the end_ */
-static void range_set_append_unsafe(struct range_set *rs, long a, long b)
+void range_set_append_unsafe(struct range_set *rs, long a, long b)
{
assert(a <= b);
range_set_grow(rs, 1);
rs->nr++;
}
-static void range_set_append(struct range_set *rs, long a, long b)
+void range_set_append(struct range_set *rs, long a, long b)
{
assert(rs->nr == 0 || rs->ranges[rs->nr-1].end <= a);
range_set_append_unsafe(rs, a, b);
* In-place pass of sorting and merging the ranges in the range set,
* to establish the invariants when we get the ranges from the user
*/
-static void sort_and_merge_range_set(struct range_set *rs)
+void sort_and_merge_range_set(struct range_set *rs)
{
int i;
int o = 0; /* output cursor */
if (p) {
range_set_append_unsafe(&p->ranges, begin, end);
- sort_and_merge_range_set(&p->ranges);
free(path);
return;
}
p = xcalloc(1, sizeof(struct line_log_data));
p->path = path;
range_set_append(&p->ranges, begin, end);
- sort_and_merge_range_set(&p->ranges);
if (ip) {
p->next = ip->next;
ip->next = p;
struct nth_line_cb cb_data;
struct string_list_item *item;
struct line_log_data *ranges = NULL;
+ struct line_log_data *p;
for_each_string_list_item(item, args) {
const char *name_part, *range_part;
char *full_name;
struct diff_filespec *spec;
long begin = 0, end = 0;
+ long anchor;
name_part = skip_range_arg(item->string);
if (!name_part || *name_part != ':' || !name_part[1])
cb_data.lines = lines;
cb_data.line_ends = ends;
+ p = search_line_log_data(ranges, full_name, NULL);
+ if (p && p->ranges.nr)
+ anchor = p->ranges.ranges[p->ranges.nr - 1].end + 1;
+ else
+ anchor = 1;
+
if (parse_range_arg(range_part, nth_line, &cb_data,
- lines, &begin, &end,
+ lines, anchor, &begin, &end,
full_name))
die("malformed -L argument '%s'", range_part);
+ if (lines < end || ((lines || begin) && lines < begin))
+ die("file %s has only %lu lines", name_part, lines);
if (begin < 1)
begin = 1;
if (end < 1)
end = lines;
begin--;
- if (lines < end || lines < begin)
- die("file %s has only %ld lines", name_part, lines);
line_log_data_insert(&ranges, full_name, begin, end);
free_filespec(spec);
ends = NULL;
}
+ for (p = ranges; p; p = p->next)
+ sort_and_merge_range_set(&p->ranges);
+
return ranges;
}
r = r->next;
}
paths[count] = NULL;
- init_pathspec(&rev->diffopt.pathspec, paths);
+ parse_pathspec(&rev->diffopt.pathspec, 0, 0, "", paths);
free(paths);
}
}
struct range_set target;
};
+extern void range_set_init(struct range_set *, size_t prealloc);
+extern void range_set_release(struct range_set *);
+/* Range includes start; excludes end */
+extern void range_set_append_unsafe(struct range_set *, long start, long end);
+/* New range must begin at or after end of last added range */
+extern void range_set_append(struct range_set *, long start, long end);
+/*
+ * In-place pass of sorting and merging the ranges in the range set,
+ * to sort and make the ranges disjoint.
+ */
+extern void sort_and_merge_range_set(struct range_set *);
+
/* Linked list of interesting files and their associated ranges. The
* list must be kept sorted by path.
*
/*
* Parse one item in the -L option
+ *
+ * 'begin' is applicable only to relative range anchors. Absolute anchors
+ * ignore this value.
+ *
+ * When parsing "-L A,B", parse_loc() is called once for A and once for B.
+ *
+ * When parsing A, 'begin' must be a negative number, the absolute value of
+ * which is the line at which relative start-of-range anchors should be
+ * based. Beginning of file is represented by -1.
+ *
+ * When parsing B, 'begin' must be the positive line number immediately
+ * following the line computed for 'A'.
*/
static const char *parse_loc(const char *spec, nth_line_fn_t nth_line,
void *data, long lines, long begin, long *ret)
* for 20 lines, or "-L <something>,-5" for 5 lines ending at
* <something>.
*/
- if (1 < begin && (spec[0] == '+' || spec[0] == '-')) {
+ if (1 <= begin && (spec[0] == '+' || spec[0] == '-')) {
num = strtol(spec + 1, &term, 10);
if (term != spec + 1) {
if (!ret)
return term;
+ if (num == 0)
+ die("-L invalid empty range");
if (spec[0] == '-')
num = 0 - num;
if (0 < num)
}
num = strtol(spec, &term, 10);
if (term != spec) {
- if (ret)
+ if (ret) {
+ if (num <= 0)
+ die("-L invalid line number: %ld", num);
*ret = num;
+ }
return term;
}
+
+ if (begin < 0) {
+ if (spec[0] != '^')
+ begin = -begin;
+ else {
+ begin = 1;
+ spec++;
+ }
+ }
+
if (spec[0] != '/')
return spec;
else {
char errbuf[1024];
regerror(reg_error, ®exp, errbuf, 1024);
- die("-L parameter '%s': %s", spec + 1, errbuf);
+ die("-L parameter '%s' starting at line %ld: %s",
+ spec + 1, begin + 1, errbuf);
}
}
}
static const char *parse_range_funcname(const char *arg, nth_line_fn_t nth_line_cb,
- void *cb_data, long lines, long *begin, long *end,
+ void *cb_data, long lines, long anchor, long *begin, long *end,
const char *path)
{
char *pattern;
int reg_error;
regex_t regexp;
+ if (*arg == '^') {
+ anchor = 1;
+ arg++;
+ }
+
assert(*arg == ':');
term = arg+1;
while (*term && *term != ':') {
pattern = xstrndup(arg+1, term-(arg+1));
- start = nth_line_cb(cb_data, 0);
+ anchor--; /* input is in human terms */
+ start = nth_line_cb(cb_data, anchor);
drv = userdiff_find_by_path(path);
if (drv && drv->funcname.pattern) {
p = find_funcname_matching_regexp(xecfg, (char*) start, ®exp);
if (!p)
- die("-L parameter '%s': no match", pattern);
+ die("-L parameter '%s' starting at line %ld: no match",
+ pattern, anchor + 1);
*begin = 0;
while (p > nth_line_cb(cb_data, *begin))
(*begin)++;
}
int parse_range_arg(const char *arg, nth_line_fn_t nth_line_cb,
- void *cb_data, long lines, long *begin, long *end,
- const char *path)
+ void *cb_data, long lines, long anchor,
+ long *begin, long *end, const char *path)
{
*begin = *end = 0;
- if (*arg == ':') {
- arg = parse_range_funcname(arg, nth_line_cb, cb_data, lines, begin, end, path);
+ if (anchor < 1)
+ anchor = 1;
+ if (anchor > lines)
+ anchor = lines + 1;
+
+ if (*arg == ':' || (*arg == '^' && *(arg + 1) == ':')) {
+ arg = parse_range_funcname(arg, nth_line_cb, cb_data, lines, anchor, begin, end, path);
if (!arg || *arg)
return -1;
return 0;
}
- arg = parse_loc(arg, nth_line_cb, cb_data, lines, 1, begin);
+ arg = parse_loc(arg, nth_line_cb, cb_data, lines, -anchor, begin);
if (*arg == ',')
arg = parse_loc(arg + 1, nth_line_cb, cb_data, lines, *begin + 1, end);
const char *skip_range_arg(const char *arg)
{
- if (*arg == ':')
- return parse_range_funcname(arg, NULL, NULL, 0, NULL, NULL, NULL);
+ if (*arg == ':' || (*arg == '^' && *(arg + 1) == ':'))
+ return parse_range_funcname(arg, NULL, NULL, 0, 0, NULL, NULL, NULL);
arg = parse_loc(arg, NULL, NULL, 0, -1, NULL);
* line 'lno' inside the 'cb_data'. The caller is expected to already
* have a suitable map at hand to make this a constant-time lookup.
*
+ * 'anchor' is the 1-based line at which relative range specifications
+ * should be anchored. Absolute ranges are unaffected by this value.
+ *
* Returns 0 in case of success and -1 if there was an error. The
* actual range is stored in *begin and *end. The counting starts
* at 1! In case of error, the caller should show usage message.
extern int parse_range_arg(const char *arg,
nth_line_fn_t nth_line_cb,
- void *cb_data, long lines,
+ void *cb_data, long lines, long anchor,
long *begin, long *end,
const char *path);
cb_data);
}
strbuf_setlen(base, baselen);
- free(tree->buffer);
- tree->buffer = NULL;
+ free_tree_buffer(tree);
}
static void mark_edge_parents_uninteresting(struct commit *commit,
}
}
-void mark_edges_uninteresting(struct commit_list *list,
- struct rev_info *revs,
- show_edge_fn show_edge)
+void mark_edges_uninteresting(struct rev_info *revs, show_edge_fn show_edge)
{
- for ( ; list; list = list->next) {
+ struct commit_list *list;
+ int i;
+
+ for (list = revs->commits; list; list = list->next) {
struct commit *commit = list->item;
if (commit->object.flags & UNINTERESTING) {
mark_tree_uninteresting(commit->tree);
+ if (revs->edge_hint && !(commit->object.flags & SHOWN)) {
+ commit->object.flags |= SHOWN;
+ show_edge(commit);
+ }
continue;
}
mark_edge_parents_uninteresting(commit, revs, show_edge);
}
+ for (i = 0; i < revs->cmdline.nr; i++) {
+ struct object *obj = revs->cmdline.rev[i].item;
+ struct commit *commit = (struct commit *)obj;
+ if (obj->type != OBJ_COMMIT || !(obj->flags & UNINTERESTING))
+ continue;
+ mark_tree_uninteresting(commit->tree);
+ if (revs->edge_hint && !(obj->flags & SHOWN)) {
+ obj->flags |= SHOWN;
+ show_edge(commit);
+ }
+ }
}
static void add_pending_tree(struct rev_info *revs, struct tree *tree)
void traverse_commit_list(struct rev_info *, show_commit_fn, show_object_fn, void *);
typedef void (*show_edge_fn)(struct commit *);
-void mark_edges_uninteresting(struct commit_list *, struct rev_info *, show_edge_fn);
+void mark_edges_uninteresting(struct rev_info *, show_edge_fn);
#endif
sha1 = commit->tree->object.sha1;
/* Root commit? */
- parents = commit->parents;
+ parents = get_saved_parents(opt, commit);
if (!parents) {
if (opt->show_root_diff) {
diff_root_tree_sha1(sha1, "", &opt->diffopt);
if (!strncmp(buffer, abbrev, abblen)) {
char *cp;
- if (repo_abbrev)
- free(*repo_abbrev);
+ free(*repo_abbrev);
*repo_abbrev = xmalloc(len);
for (cp = buffer + abblen; isspace(*cp); cp++)
return 0;
}
-static void read_mailmap_buf(struct string_list *map,
- const char *buf, unsigned long len,
- char **repo_abbrev)
+static void read_mailmap_string(struct string_list *map, char *buf,
+ char **repo_abbrev)
{
- while (len) {
- const char *end = strchrnul(buf, '\n');
- unsigned long linelen = end - buf + 1;
- char *line = xmemdupz(buf, linelen);
+ while (*buf) {
+ char *end = strchrnul(buf, '\n');
- read_mailmap_line(map, line, repo_abbrev);
+ if (*end)
+ *end++ = '\0';
- free(line);
- buf += linelen;
- len -= linelen;
+ read_mailmap_line(map, buf, repo_abbrev);
+ buf = end;
}
}
if (type != OBJ_BLOB)
return error("mailmap is not a blob: %s", name);
- read_mailmap_buf(map, buf, size, repo_abbrev);
+ read_mailmap_string(map, buf, repo_abbrev);
free(buf);
return 0;
{
int n;
struct pathspec match_all;
- init_pathspec(&match_all, NULL);
+ memset(&match_all, 0, sizeof(match_all));
if (read_tree_recursive(tree, "", 0, 0, &match_all, save_files_dirs, o))
return 0;
n = o->current_file_set.nr + o->current_directory_set.nr;
sha1_to_hex(mp->remote));
}
diff_flush(&opt);
- diff_tree_release_paths(&opt);
+ free_pathspec(&opt.pathspec);
*num_changes = len;
return changes;
sha1_to_hex(mp->local));
}
diff_flush(&opt);
- diff_tree_release_paths(&opt);
+ free_pathspec(&opt.pathspec);
}
static void check_notes_merge_worktree(struct notes_merge_options *o)
* Properties of the created commit:
* - tree: the result of converting t to a tree object with write_notes_tree().
* - parents: the given parents OR (if NULL) the commit referenced by t->ref.
- * - author/committer: the default determined by commmit_tree().
+ * - author/committer: the default determined by commit_tree().
* - commit message: msg
*
* The resulting commit SHA1 is stored in result_sha1.
die("invalid object type \"%s\"", str);
}
-static unsigned int hash_obj(struct object *obj, unsigned int n)
+static unsigned int hash_obj(const unsigned char *sha1, unsigned int n)
{
unsigned int hash;
- memcpy(&hash, obj->sha1, sizeof(unsigned int));
- return hash % n;
+ memcpy(&hash, sha1, sizeof(unsigned int));
+ /* Assumes power-of-2 hash sizes in grow_object_hash */
+ return hash & (n - 1);
}
static void insert_obj_hash(struct object *obj, struct object **hash, unsigned int size)
{
- unsigned int j = hash_obj(obj, size);
+ unsigned int j = hash_obj(obj->sha1, size);
while (hash[j]) {
j++;
hash[j] = obj;
}
-static unsigned int hashtable_index(const unsigned char *sha1)
-{
- unsigned int i;
- memcpy(&i, sha1, sizeof(unsigned int));
- return i % obj_hash_size;
-}
-
struct object *lookup_object(const unsigned char *sha1)
{
unsigned int i, first;
if (!obj_hash)
return NULL;
- first = i = hashtable_index(sha1);
+ first = i = hash_obj(sha1, obj_hash_size);
while ((obj = obj_hash[i]) != NULL) {
if (!hashcmp(sha1, obj->sha1))
break;
static void grow_object_hash(void)
{
int i;
+ /*
+ * Note that this size must always be power-of-2 to match hash_obj
+ * above.
+ */
int new_hash_size = obj_hash_size < 32 ? 32 : 2 * obj_hash_size;
struct object **new_hash;
pager = getenv("PAGER");
if (!pager)
pager = DEFAULT_PAGER;
- else if (!*pager || !strcmp(pager, "cat"))
+ if (!*pager || !strcmp(pager, "cat"))
pager = NULL;
return pager;
*
* Note that this function is purely textual. It does not follow symlinks,
* verify the existence of the path, or make any system calls.
+ *
+ * prefix_len != NULL is for a specific case of prefix_pathspec():
+ * assume that src == dst and src[0..prefix_len-1] is already
+ * normalized, any time "../" eats up to the prefix_len part,
+ * prefix_len is reduced. In the end prefix_len is the remaining
+ * prefix that has not been overridden by user pathspec.
*/
-int normalize_path_copy(char *dst, const char *src)
+int normalize_path_copy_len(char *dst, const char *src, int *prefix_len)
{
char *dst0;
/* Windows: dst[-1] cannot be backslash anymore */
while (dst0 < dst && dst[-1] != '/')
dst--;
+ if (prefix_len && *prefix_len > dst - dst0)
+ *prefix_len = dst - dst0;
}
*dst = '\0';
return 0;
}
+int normalize_path_copy(char *dst, const char *src)
+{
+ return normalize_path_copy_len(dst, src, NULL);
+}
+
/*
* path = Canonical absolute path
* prefixes = string_list containing normalized, absolute paths without
* If seen[] has not already been written to, it may make sense
* to use find_pathspecs_matching_against_index() instead.
*/
-void add_pathspec_matches_against_index(const char **pathspec,
- char *seen, int specs)
+void add_pathspec_matches_against_index(const struct pathspec *pathspec,
+ char *seen)
{
int num_unmatched = 0, i;
* mistakenly think that the user gave a pathspec that did not match
* anything.
*/
- for (i = 0; i < specs; i++)
+ for (i = 0; i < pathspec->nr; i++)
if (!seen[i])
num_unmatched++;
if (!num_unmatched)
return;
for (i = 0; i < active_nr; i++) {
const struct cache_entry *ce = active_cache[i];
- match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen);
+ match_pathspec_depth(pathspec, ce->name, ce_namelen(ce), 0, seen);
}
}
* nature of the "closest" (i.e. most specific) matches which each of the
* given pathspecs achieves against all items in the index.
*/
-char *find_pathspecs_matching_against_index(const char **pathspec)
+char *find_pathspecs_matching_against_index(const struct pathspec *pathspec)
{
- char *seen;
- int i;
-
- for (i = 0; pathspec[i]; i++)
- ; /* just counting */
- seen = xcalloc(i, 1);
- add_pathspec_matches_against_index(pathspec, seen, i);
+ char *seen = xcalloc(pathspec->nr, 1);
+ add_pathspec_matches_against_index(pathspec, seen);
return seen;
}
/*
- * Check the index to see whether path refers to a submodule, or
- * something inside a submodule. If the former, returns the path with
- * any trailing slash stripped. If the latter, dies with an error
- * message.
+ * Magic pathspec
+ *
+ * Possible future magic semantics include stuff like:
+ *
+ * { PATHSPEC_RECURSIVE, '*', "recursive" },
+ * { PATHSPEC_REGEXP, '\0', "regexp" },
+ *
+ */
+
+static struct pathspec_magic {
+ unsigned bit;
+ char mnemonic; /* this cannot be ':'! */
+ const char *name;
+} pathspec_magic[] = {
+ { PATHSPEC_FROMTOP, '/', "top" },
+ { PATHSPEC_LITERAL, 0, "literal" },
+ { PATHSPEC_GLOB, '\0', "glob" },
+ { PATHSPEC_ICASE, '\0', "icase" },
+};
+
+/*
+ * Take an element of a pathspec and check for magic signatures.
+ * Append the result to the prefix. Return the magic bitmap.
+ *
+ * For now, we only parse the syntax and throw out anything other than
+ * "top" magic.
+ *
+ * NEEDSWORK: This needs to be rewritten when we start migrating
+ * get_pathspec() users to use the "struct pathspec" interface. For
+ * example, a pathspec element may be marked as case-insensitive, but
+ * the prefix part must always match literally, and a single stupid
+ * string cannot express such a case.
*/
-const char *check_path_for_gitlink(const char *path)
+static unsigned prefix_pathspec(struct pathspec_item *item,
+ unsigned *p_short_magic,
+ const char **raw, unsigned flags,
+ const char *prefix, int prefixlen,
+ const char *elt)
{
- int i, path_len = strlen(path);
- for (i = 0; i < active_nr; i++) {
- const struct cache_entry *ce = active_cache[i];
- if (S_ISGITLINK(ce->ce_mode)) {
- int ce_len = ce_namelen(ce);
- if (path_len <= ce_len || path[ce_len] != '/' ||
- memcmp(ce->name, path, ce_len))
- /* path does not refer to this
- * submodule or anything inside it */
+ static int literal_global = -1;
+ static int glob_global = -1;
+ static int noglob_global = -1;
+ static int icase_global = -1;
+ unsigned magic = 0, short_magic = 0, global_magic = 0;
+ const char *copyfrom = elt, *long_magic_end = NULL;
+ char *match;
+ int i, pathspec_prefix = -1;
+
+ if (literal_global < 0)
+ literal_global = git_env_bool(GIT_LITERAL_PATHSPECS_ENVIRONMENT, 0);
+ if (literal_global)
+ global_magic |= PATHSPEC_LITERAL;
+
+ if (glob_global < 0)
+ glob_global = git_env_bool(GIT_GLOB_PATHSPECS_ENVIRONMENT, 0);
+ if (glob_global)
+ global_magic |= PATHSPEC_GLOB;
+
+ if (noglob_global < 0)
+ noglob_global = git_env_bool(GIT_NOGLOB_PATHSPECS_ENVIRONMENT, 0);
+
+ if (glob_global && noglob_global)
+ die(_("global 'glob' and 'noglob' pathspec settings are incompatible"));
+
+
+ if (icase_global < 0)
+ icase_global = git_env_bool(GIT_ICASE_PATHSPECS_ENVIRONMENT, 0);
+ if (icase_global)
+ global_magic |= PATHSPEC_ICASE;
+
+ if ((global_magic & PATHSPEC_LITERAL) &&
+ (global_magic & ~PATHSPEC_LITERAL))
+ die(_("global 'literal' pathspec setting is incompatible "
+ "with all other global pathspec settings"));
+
+ if (elt[0] != ':' || literal_global) {
+ ; /* nothing to do */
+ } else if (elt[1] == '(') {
+ /* longhand */
+ const char *nextat;
+ for (copyfrom = elt + 2;
+ *copyfrom && *copyfrom != ')';
+ copyfrom = nextat) {
+ size_t len = strcspn(copyfrom, ",)");
+ if (copyfrom[len] == ',')
+ nextat = copyfrom + len + 1;
+ else
+ /* handle ')' and '\0' */
+ nextat = copyfrom + len;
+ if (!len)
continue;
- if (path_len == ce_len + 1) {
- /* path refers to submodule;
- * strip trailing slash */
- return xstrndup(ce->name, ce_len);
+ for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) {
+ if (strlen(pathspec_magic[i].name) == len &&
+ !strncmp(pathspec_magic[i].name, copyfrom, len)) {
+ magic |= pathspec_magic[i].bit;
+ break;
+ }
+ if (!prefixcmp(copyfrom, "prefix:")) {
+ char *endptr;
+ pathspec_prefix = strtol(copyfrom + 7,
+ &endptr, 10);
+ if (endptr - copyfrom != len)
+ die(_("invalid parameter for pathspec magic 'prefix'"));
+ /* "i" would be wrong, but it does not matter */
+ break;
+ }
+ }
+ if (ARRAY_SIZE(pathspec_magic) <= i)
+ die(_("Invalid pathspec magic '%.*s' in '%s'"),
+ (int) len, copyfrom, elt);
+ }
+ if (*copyfrom != ')')
+ die(_("Missing ')' at the end of pathspec magic in '%s'"), elt);
+ long_magic_end = copyfrom;
+ copyfrom++;
+ } else {
+ /* shorthand */
+ for (copyfrom = elt + 1;
+ *copyfrom && *copyfrom != ':';
+ copyfrom++) {
+ char ch = *copyfrom;
+
+ if (!is_pathspec_magic(ch))
+ break;
+ for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++)
+ if (pathspec_magic[i].mnemonic == ch) {
+ short_magic |= pathspec_magic[i].bit;
+ break;
+ }
+ if (ARRAY_SIZE(pathspec_magic) <= i)
+ die(_("Unimplemented pathspec magic '%c' in '%s'"),
+ ch, elt);
+ }
+ if (*copyfrom == ':')
+ copyfrom++;
+ }
+
+ magic |= short_magic;
+ *p_short_magic = short_magic;
+
+ /* --noglob-pathspec adds :(literal) _unless_ :(glob) is specifed */
+ if (noglob_global && !(magic & PATHSPEC_GLOB))
+ global_magic |= PATHSPEC_LITERAL;
+
+ /* --glob-pathspec is overriden by :(literal) */
+ if ((global_magic & PATHSPEC_GLOB) && (magic & PATHSPEC_LITERAL))
+ global_magic &= ~PATHSPEC_GLOB;
+
+ magic |= global_magic;
+
+ if (pathspec_prefix >= 0 &&
+ (prefixlen || (prefix && *prefix)))
+ die("BUG: 'prefix' magic is supposed to be used at worktree's root");
+
+ if ((magic & PATHSPEC_LITERAL) && (magic & PATHSPEC_GLOB))
+ die(_("%s: 'literal' and 'glob' are incompatible"), elt);
+
+ if (pathspec_prefix >= 0) {
+ match = xstrdup(copyfrom);
+ prefixlen = pathspec_prefix;
+ } else if (magic & PATHSPEC_FROMTOP) {
+ match = xstrdup(copyfrom);
+ prefixlen = 0;
+ } else {
+ match = prefix_path_gently(prefix, prefixlen, &prefixlen, copyfrom);
+ if (!match)
+ die(_("%s: '%s' is outside repository"), elt, copyfrom);
+ }
+ *raw = item->match = match;
+ /*
+ * Prefix the pathspec (keep all magic) and assign to
+ * original. Useful for passing to another command.
+ */
+ if (flags & PATHSPEC_PREFIX_ORIGIN) {
+ struct strbuf sb = STRBUF_INIT;
+ const char *start = elt;
+ if (prefixlen && !literal_global) {
+ /* Preserve the actual prefix length of each pattern */
+ if (short_magic)
+ die("BUG: prefixing on short magic is not supported");
+ else if (long_magic_end) {
+ strbuf_add(&sb, start, long_magic_end - start);
+ strbuf_addf(&sb, ",prefix:%d", prefixlen);
+ start = long_magic_end;
} else {
- die (_("Path '%s' is in submodule '%.*s'"),
- path, ce_len, ce->name);
+ if (*start == ':')
+ start++;
+ strbuf_addf(&sb, ":(prefix:%d)", prefixlen);
}
}
+ strbuf_add(&sb, start, copyfrom - start);
+ strbuf_addstr(&sb, match);
+ item->original = strbuf_detach(&sb, NULL);
+ } else
+ item->original = elt;
+ item->len = strlen(item->match);
+ item->prefix = prefixlen;
+
+ if ((flags & PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP) &&
+ (item->len >= 1 && item->match[item->len - 1] == '/') &&
+ (i = cache_name_pos(item->match, item->len - 1)) >= 0 &&
+ S_ISGITLINK(active_cache[i]->ce_mode)) {
+ item->len--;
+ match[item->len] = '\0';
+ }
+
+ if (flags & PATHSPEC_STRIP_SUBMODULE_SLASH_EXPENSIVE)
+ for (i = 0; i < active_nr; i++) {
+ struct cache_entry *ce = active_cache[i];
+ int ce_len = ce_namelen(ce);
+
+ if (!S_ISGITLINK(ce->ce_mode))
+ continue;
+
+ if (item->len <= ce_len || match[ce_len] != '/' ||
+ memcmp(ce->name, match, ce_len))
+ continue;
+ if (item->len == ce_len + 1) {
+ /* strip trailing slash */
+ item->len--;
+ match[item->len] = '\0';
+ } else
+ die (_("Pathspec '%s' is in submodule '%.*s'"),
+ elt, ce_len, ce->name);
+ }
+
+ if (magic & PATHSPEC_LITERAL)
+ item->nowildcard_len = item->len;
+ else {
+ item->nowildcard_len = simple_length(item->match);
+ if (item->nowildcard_len < prefixlen)
+ item->nowildcard_len = prefixlen;
+ }
+ item->flags = 0;
+ if (magic & PATHSPEC_GLOB) {
+ /*
+ * FIXME: should we enable ONESTAR in _GLOB for
+ * pattern "* * / * . c"?
+ */
+ } else {
+ if (item->nowildcard_len < item->len &&
+ item->match[item->nowildcard_len] == '*' &&
+ no_wildcard(item->match + item->nowildcard_len + 1))
+ item->flags |= PATHSPEC_ONESTAR;
}
- return path;
+
+ /* sanity checks, pathspec matchers assume these are sane */
+ assert(item->nowildcard_len <= item->len &&
+ item->prefix <= item->len);
+ return magic;
+}
+
+static int pathspec_item_cmp(const void *a_, const void *b_)
+{
+ struct pathspec_item *a, *b;
+
+ a = (struct pathspec_item *)a_;
+ b = (struct pathspec_item *)b_;
+ return strcmp(a->match, b->match);
+}
+
+static void NORETURN unsupported_magic(const char *pattern,
+ unsigned magic,
+ unsigned short_magic)
+{
+ struct strbuf sb = STRBUF_INIT;
+ int i, n;
+ for (n = i = 0; i < ARRAY_SIZE(pathspec_magic); i++) {
+ const struct pathspec_magic *m = pathspec_magic + i;
+ if (!(magic & m->bit))
+ continue;
+ if (sb.len)
+ strbuf_addstr(&sb, " ");
+ if (short_magic & m->bit)
+ strbuf_addf(&sb, "'%c'", m->mnemonic);
+ else
+ strbuf_addf(&sb, "'%s'", m->name);
+ n++;
+ }
+ /*
+ * We may want to substitute "this command" with a command
+ * name. E.g. when add--interactive dies when running
+ * "checkout -p"
+ */
+ die(_("%s: pathspec magic not supported by this command: %s"),
+ pattern, sb.buf);
}
/*
- * Dies if the given path refers to a file inside a symlinked
- * directory in the index.
+ * Given command line arguments and a prefix, convert the input to
+ * pathspec. die() if any magic in magic_mask is used.
*/
-void die_if_path_beyond_symlink(const char *path, const char *prefix)
+void parse_pathspec(struct pathspec *pathspec,
+ unsigned magic_mask, unsigned flags,
+ const char *prefix, const char **argv)
{
- if (has_symlink_leading_path(path, strlen(path))) {
- int len = prefix ? strlen(prefix) : 0;
- die(_("'%s' is beyond a symbolic link"), path + len);
+ struct pathspec_item *item;
+ const char *entry = argv ? *argv : NULL;
+ int i, n, prefixlen;
+
+ memset(pathspec, 0, sizeof(*pathspec));
+
+ if (flags & PATHSPEC_MAXDEPTH_VALID)
+ pathspec->magic |= PATHSPEC_MAXDEPTH;
+
+ /* No arguments, no prefix -> no pathspec */
+ if (!entry && !prefix)
+ return;
+
+ if ((flags & PATHSPEC_PREFER_CWD) &&
+ (flags & PATHSPEC_PREFER_FULL))
+ die("BUG: PATHSPEC_PREFER_CWD and PATHSPEC_PREFER_FULL are incompatible");
+
+ /* No arguments with prefix -> prefix pathspec */
+ if (!entry) {
+ static const char *raw[2];
+
+ if (flags & PATHSPEC_PREFER_FULL)
+ return;
+
+ if (!(flags & PATHSPEC_PREFER_CWD))
+ die("BUG: PATHSPEC_PREFER_CWD requires arguments");
+
+ pathspec->items = item = xmalloc(sizeof(*item));
+ memset(item, 0, sizeof(*item));
+ item->match = prefix;
+ item->original = prefix;
+ item->nowildcard_len = item->len = strlen(prefix);
+ item->prefix = item->len;
+ raw[0] = prefix;
+ raw[1] = NULL;
+ pathspec->nr = 1;
+ pathspec->_raw = raw;
+ return;
}
+
+ n = 0;
+ while (argv[n])
+ n++;
+
+ pathspec->nr = n;
+ pathspec->items = item = xmalloc(sizeof(*item) * n);
+ pathspec->_raw = argv;
+ prefixlen = prefix ? strlen(prefix) : 0;
+
+ for (i = 0; i < n; i++) {
+ unsigned short_magic;
+ entry = argv[i];
+
+ item[i].magic = prefix_pathspec(item + i, &short_magic,
+ argv + i, flags,
+ prefix, prefixlen, entry);
+ if (item[i].magic & magic_mask)
+ unsupported_magic(entry,
+ item[i].magic & magic_mask,
+ short_magic);
+
+ if ((flags & PATHSPEC_SYMLINK_LEADING_PATH) &&
+ has_symlink_leading_path(item[i].match, item[i].len)) {
+ die(_("pathspec '%s' is beyond a symbolic link"), entry);
+ }
+
+ if (item[i].nowildcard_len < item[i].len)
+ pathspec->has_wildcard = 1;
+ pathspec->magic |= item[i].magic;
+ }
+
+
+ if (pathspec->magic & PATHSPEC_MAXDEPTH) {
+ if (flags & PATHSPEC_KEEP_ORDER)
+ die("BUG: PATHSPEC_MAXDEPTH_VALID and PATHSPEC_KEEP_ORDER are incompatible");
+ qsort(pathspec->items, pathspec->nr,
+ sizeof(struct pathspec_item), pathspec_item_cmp);
+ }
+}
+
+/*
+ * N.B. get_pathspec() is deprecated in favor of the "struct pathspec"
+ * based interface - see pathspec.c:parse_pathspec().
+ *
+ * Arguments:
+ * - prefix - a path relative to the root of the working tree
+ * - pathspec - a list of paths underneath the prefix path
+ *
+ * Iterates over pathspec, prepending each path with prefix,
+ * and return the resulting list.
+ *
+ * If pathspec is empty, return a singleton list containing prefix.
+ *
+ * If pathspec and prefix are both empty, return an empty list.
+ *
+ * This is typically used by built-in commands such as add.c, in order
+ * to normalize argv arguments provided to the built-in into a list of
+ * paths to process, all relative to the root of the working tree.
+ */
+const char **get_pathspec(const char *prefix, const char **pathspec)
+{
+ struct pathspec ps;
+ parse_pathspec(&ps,
+ PATHSPEC_ALL_MAGIC &
+ ~(PATHSPEC_FROMTOP | PATHSPEC_LITERAL),
+ PATHSPEC_PREFER_CWD,
+ prefix, pathspec);
+ return ps._raw;
+}
+
+void copy_pathspec(struct pathspec *dst, const struct pathspec *src)
+{
+ *dst = *src;
+ dst->items = xmalloc(sizeof(struct pathspec_item) * dst->nr);
+ memcpy(dst->items, src->items,
+ sizeof(struct pathspec_item) * dst->nr);
+}
+
+void free_pathspec(struct pathspec *pathspec)
+{
+ free(pathspec->items);
+ pathspec->items = NULL;
}
#ifndef PATHSPEC_H
#define PATHSPEC_H
-extern char *find_pathspecs_matching_against_index(const char **pathspec);
-extern void add_pathspec_matches_against_index(const char **pathspec, char *seen, int specs);
+/* Pathspec magic */
+#define PATHSPEC_FROMTOP (1<<0)
+#define PATHSPEC_MAXDEPTH (1<<1)
+#define PATHSPEC_LITERAL (1<<2)
+#define PATHSPEC_GLOB (1<<3)
+#define PATHSPEC_ICASE (1<<4)
+#define PATHSPEC_ALL_MAGIC \
+ (PATHSPEC_FROMTOP | \
+ PATHSPEC_MAXDEPTH | \
+ PATHSPEC_LITERAL | \
+ PATHSPEC_GLOB | \
+ PATHSPEC_ICASE)
+
+#define PATHSPEC_ONESTAR 1 /* the pathspec pattern satisfies GFNM_ONESTAR */
+
+struct pathspec {
+ const char **_raw; /* get_pathspec() result, not freed by free_pathspec() */
+ int nr;
+ unsigned int has_wildcard:1;
+ unsigned int recursive:1;
+ unsigned magic;
+ int max_depth;
+ struct pathspec_item {
+ const char *match;
+ const char *original;
+ unsigned magic;
+ int len, prefix;
+ int nowildcard_len;
+ int flags;
+ } *items;
+};
+
+#define GUARD_PATHSPEC(ps, mask) \
+ do { \
+ if ((ps)->magic & ~(mask)) \
+ die("BUG:%s:%d: unsupported magic %x", \
+ __FILE__, __LINE__, (ps)->magic & ~(mask)); \
+ } while (0)
+
+/* parse_pathspec flags */
+#define PATHSPEC_PREFER_CWD (1<<0) /* No args means match cwd */
+#define PATHSPEC_PREFER_FULL (1<<1) /* No args means match everything */
+#define PATHSPEC_MAXDEPTH_VALID (1<<2) /* max_depth field is valid */
+/* strip the trailing slash if the given path is a gitlink */
+#define PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP (1<<3)
+/* die if a symlink is part of the given path's directory */
+#define PATHSPEC_SYMLINK_LEADING_PATH (1<<4)
+/*
+ * This is like a combination of ..LEADING_PATH and .._SLASH_CHEAP
+ * (but not the same): it strips the trailing slash if the given path
+ * is a gitlink but also checks and dies if gitlink is part of the
+ * leading path (i.e. the given path goes beyond a submodule). It's
+ * safer than _SLASH_CHEAP and also more expensive.
+ */
+#define PATHSPEC_STRIP_SUBMODULE_SLASH_EXPENSIVE (1<<5)
+#define PATHSPEC_PREFIX_ORIGIN (1<<6)
+#define PATHSPEC_KEEP_ORDER (1<<7)
+
+extern void parse_pathspec(struct pathspec *pathspec,
+ unsigned magic_mask,
+ unsigned flags,
+ const char *prefix,
+ const char **args);
+extern void copy_pathspec(struct pathspec *dst, const struct pathspec *src);
+extern void free_pathspec(struct pathspec *);
+
+static inline int ps_strncmp(const struct pathspec_item *item,
+ const char *s1, const char *s2, size_t n)
+{
+ if (item->magic & PATHSPEC_ICASE)
+ return strncasecmp(s1, s2, n);
+ else
+ return strncmp(s1, s2, n);
+}
+
+static inline int ps_strcmp(const struct pathspec_item *item,
+ const char *s1, const char *s2)
+{
+ if (item->magic & PATHSPEC_ICASE)
+ return strcasecmp(s1, s2);
+ else
+ return strcmp(s1, s2);
+}
+
+extern char *find_pathspecs_matching_against_index(const struct pathspec *pathspec);
+extern void add_pathspec_matches_against_index(const struct pathspec *pathspec, char *seen);
extern const char *check_path_for_gitlink(const char *path);
extern void die_if_path_beyond_symlink(const char *path, const char *prefix);
}
}
+# serf has a bug that leads to a coredump upon termination if the
+# remote access object is left around (not fixed yet in serf 1.3.1).
+# Explicitly free it to work around the issue.
+END {
+ $RA = undef;
+ $ra_invalid = 1;
+}
+
sub _auth_providers () {
my @rv = (
SVN::Client::get_simple_provider(),
msgstr ""
#: builtin/describe.c:482
-msgid "--dirty is incompatible with committishes"
+msgid "--dirty is incompatible with commit-ishes"
msgstr ""
#: builtin/diff.c:77
msgstr "gibt Größenangaben in menschenlesbaren Format aus"
#: builtin/describe.c:16
-msgid "git describe [options] <committish>*"
-msgstr "git describe [Optionen] <committish>*"
+msgid "git describe [options] <commit-ish>*"
+msgstr "git describe [Optionen] <commit-ish>*"
#: builtin/describe.c:17
msgid "git describe [options] --dirty"
#: builtin/describe.c:237
#, c-format
msgid "annotated tag %s not available"
-msgstr "annotierter Tag %s ist nicht verfügbar"
+msgstr "annotiertes Tag %s ist nicht verfügbar"
#: builtin/describe.c:241
#, c-format
msgid "annotated tag %s has no embedded name"
-msgstr "annotierter Tag %s hat keinen eingebetteten Namen"
+msgstr "annotiertes Tag %s hat keinen eingebetteten Namen"
#: builtin/describe.c:243
#, c-format
#: builtin/describe.c:409
msgid "find the tag that comes after the commit"
-msgstr "findet den Tag, die nach Commit kommt"
+msgstr "findet das Tag, das nach Commit kommt"
#: builtin/describe.c:410
msgid "debug search strategy on stderr"
#: builtin/describe.c:412
msgid "use any tag, even unannotated"
-msgstr "verwendet jeden Tag, auch nicht-annotierte"
+msgstr "verwendet jedes Tag, auch nicht-annotierte"
#: builtin/describe.c:413
msgid "always use long format"
msgstr "Keine Namen gefunden, kann nichts beschreiben."
#: builtin/describe.c:489
-msgid "--dirty is incompatible with committishes"
+msgid "--dirty is incompatible with commit-ishes"
msgstr "Die Option --dirty kann nicht mit Commits verwendet werden."
#: builtin/diff.c:79
#: builtin/fast-export.c:678
msgid "Fake a tagger when tags lack one"
-msgstr "erzeugt künstlich einen Tag-Ersteller, wenn der Tag keinen hat"
+msgstr "erzeugt künstlich einen Tag-Ersteller, wenn das Tag keinen hat"
#: builtin/fast-export.c:680
msgid "Output full tree for each commit"
#: builtin/fetch.c:324
msgid "[new tag]"
-msgstr "[neuer Tag]"
+msgstr "[neues Tag]"
#: builtin/fetch.c:327
msgid "[new branch]"
#: builtin/push.c:257
msgid "Updates were rejected because the tag already exists in the remote."
msgstr ""
-"Aktualisierungen wurden zurückgewiesen, weil der Tag bereits\n"
+"Aktualisierungen wurden zurückgewiesen, weil das Tag bereits\n"
"im Remote-Repository existiert."
#: builtin/push.c:260
#: builtin/tag.c:454
msgid "annotated tag, needs a message"
-msgstr "annotierter Tag, benötigt eine Beschreibung"
+msgstr "annotiertes Tag, benötigt eine Beschreibung"
#: builtin/tag.c:456
msgid "tag message"
#: builtin/tag.c:458
msgid "annotated and GPG-signed tag"
-msgstr "annotierter und GPG-signierter Tag"
+msgstr "annotiertes und GPG-signiertes Tag"
#: builtin/tag.c:462
msgid "use another key to sign the tag"
-msgstr "verwendet einen anderen Schlüssel um den Tag zu signieren"
+msgstr "verwendet einen anderen Schlüssel um das Tag zu signieren"
#: builtin/tag.c:463
msgid "replace the tag if exists"
-msgstr "ersetzt den Tag, wenn er existiert"
+msgstr "ersetzt das Tag, wenn es existiert"
#: builtin/tag.c:464
msgid "show tag list in columns"
msgstr ""
#: builtin/describe.c:15
-msgid "git describe [options] <committish>*"
+msgid "git describe [options] <commit-ish>*"
msgstr ""
#: builtin/describe.c:16
msgstr ""
#: builtin/describe.c:481
-msgid "--dirty is incompatible with committishes"
+msgid "--dirty is incompatible with commit-ishes"
msgstr ""
#: builtin/diff.c:79
msgstr ""
#: builtin/describe.c:16
-msgid "git describe [options] <committish>*"
+msgid "git describe [options] <commit-ish>*"
msgstr ""
#: builtin/describe.c:17
msgstr ""
#: builtin/describe.c:489
-msgid "--dirty is incompatible with committishes"
+msgid "--dirty is incompatible with commit-ishes"
msgstr ""
#: builtin/diff.c:79
msgstr ""
#: builtin/describe.c:482
-msgid "--dirty is incompatible with committishes"
+msgid "--dirty is incompatible with commit-ishes"
msgstr ""
#: builtin/diff.c:77
msgstr ""
#: builtin/describe.c:482
-msgid "--dirty is incompatible with committishes"
+msgid "--dirty is incompatible with commit-ishes"
msgstr ""
#: builtin/diff.c:77
msgstr "Nenhum nome encontrado, não descreve nada."
#: builtin/describe.c:482
-msgid "--dirty is incompatible with committishes"
+msgid "--dirty is incompatible with commit-ishes"
msgstr ""
#: builtin/diff.c:77
msgstr "skriv storlekar i människoläsbart format"
#: builtin/describe.c:16
-msgid "git describe [options] <committish>*"
+msgid "git describe [options] <commit-ish>*"
msgstr "git describe [flaggor] <incheckning-igt>*"
#: builtin/describe.c:17
msgstr "Inga namn hittades, kan inte beskriva något."
#: builtin/describe.c:489
-msgid "--dirty is incompatible with committishes"
-msgstr "--dirty är inkompatibelt med \"committish\"-värden"
+msgid "--dirty is incompatible with commit-ishes"
+msgstr "--dirty är inkompatibelt med \"commit-ish\"-värden"
#: builtin/diff.c:79
#, c-format
msgstr "hiển thị kích cỡ theo định dạng dành cho người đọc"
#: builtin/describe.c:16
-msgid "git describe [options] <committish>*"
-msgstr "git describe [các-tùy-chọn] <committish>*"
+msgid "git describe [options] <commit-ish>*"
+msgstr "git describe [các-tùy-chọn] <commit-ish>*"
#: builtin/describe.c:17
msgid "git describe [options] --dirty"
msgstr "Không tìm thấy các tên, không thể mô tả gì cả."
#: builtin/describe.c:489
-msgid "--dirty is incompatible with committishes"
+msgid "--dirty is incompatible with commit-ishes"
msgstr "--dirty là xung khắc với các tùy chọn dành cho chuyển giao (commit)"
#: builtin/diff.c:79
msgstr "以用户可读的格式显示大小"
#: builtin/describe.c:16
-msgid "git describe [options] <committish>*"
+msgid "git describe [options] <commit-ish>*"
msgstr "git describe [选项] <提交号>*"
#: builtin/describe.c:17
msgstr "没有发现名称,无法描述任何东西。"
#: builtin/describe.c:489
-msgid "--dirty is incompatible with committishes"
+msgid "--dirty is incompatible with commit-ishes"
msgstr "--dirty 不能与提交同时使用"
#: builtin/diff.c:79
* Copyright (C) 2008 Linus Torvalds
*/
#include "cache.h"
+#include "pathspec.h"
#ifdef NO_PTHREADS
-static void preload_index(struct index_state *index, const char **pathspec)
+static void preload_index(struct index_state *index,
+ const struct pathspec *pathspec)
{
; /* nothing */
}
struct thread_data {
pthread_t pthread;
struct index_state *index;
- const char **pathspec;
+ struct pathspec pathspec;
int offset, nr;
};
struct index_state *index = p->index;
struct cache_entry **cep = index->cache + p->offset;
struct cache_def cache;
- struct pathspec pathspec;
- init_pathspec(&pathspec, p->pathspec);
memset(&cache, 0, sizeof(cache));
nr = p->nr;
if (nr + p->offset > index->cache_nr)
continue;
if (ce_uptodate(ce))
continue;
- if (!ce_path_match(ce, &pathspec))
+ if (!ce_path_match(ce, &p->pathspec))
continue;
if (threaded_has_symlink_leading_path(&cache, ce->name, ce_namelen(ce)))
continue;
continue;
ce_mark_uptodate(ce);
} while (--nr > 0);
- free_pathspec(&pathspec);
return NULL;
}
-static void preload_index(struct index_state *index, const char **pathspec)
+static void preload_index(struct index_state *index,
+ const struct pathspec *pathspec)
{
int threads, i, work, offset;
struct thread_data data[MAX_PARALLEL];
threads = MAX_PARALLEL;
offset = 0;
work = DIV_ROUND_UP(index->cache_nr, threads);
+ memset(&data, 0, sizeof(data));
for (i = 0; i < threads; i++) {
struct thread_data *p = data+i;
p->index = index;
- p->pathspec = pathspec;
+ if (pathspec)
+ copy_pathspec(&p->pathspec, pathspec);
p->offset = offset;
p->nr = work;
offset += work;
}
#endif
-int read_index_preload(struct index_state *index, const char **pathspec)
+int read_index_preload(struct index_state *index,
+ const struct pathspec *pathspec)
{
int retval = read_index(index);
else
process_blob(lookup_blob(entry.sha1), p, &me, entry.path, cp);
}
- free(tree->buffer);
- tree->buffer = NULL;
+ free_tree_buffer(tree);
}
static void process_tag(struct tag *tag, struct object_array *p,
printf(fmt, name);
}
-int refresh_index(struct index_state *istate, unsigned int flags, const char **pathspec,
+int refresh_index(struct index_state *istate, unsigned int flags,
+ const struct pathspec *pathspec,
char *seen, const char *header_msg)
{
int i;
continue;
if (pathspec &&
- !match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen))
+ !match_pathspec_depth(pathspec, ce->name, ce_namelen(ce), 0, seen))
filtered = 1;
if (ce_stage(ce)) {
struct ondisk_cache_entry {
struct cache_time ctime;
struct cache_time mtime;
- unsigned int dev;
- unsigned int ino;
- unsigned int mode;
- unsigned int uid;
- unsigned int gid;
- unsigned int size;
+ uint32_t dev;
+ uint32_t ino;
+ uint32_t mode;
+ uint32_t uid;
+ uint32_t gid;
+ uint32_t size;
unsigned char sha1[20];
- unsigned short flags;
+ uint16_t flags;
char name[FLEX_ARRAY]; /* more */
};
struct ondisk_cache_entry_extended {
struct cache_time ctime;
struct cache_time mtime;
- unsigned int dev;
- unsigned int ino;
- unsigned int mode;
- unsigned int uid;
- unsigned int gid;
- unsigned int size;
+ uint32_t dev;
+ uint32_t ino;
+ uint32_t mode;
+ uint32_t uid;
+ uint32_t gid;
+ uint32_t size;
unsigned char sha1[20];
- unsigned short flags;
- unsigned short flags2;
+ uint16_t flags;
+ uint16_t flags2;
char name[FLEX_ARRAY]; /* more */
};
continue;
if (!ce_uptodate(ce) && is_racy_timestamp(istate, ce))
ce_smudge_racily_clean_entry(ce);
- if (is_null_sha1(ce->sha1))
- return error("cache entry has null sha1: %s", ce->name);
+ if (is_null_sha1(ce->sha1)) {
+ static const char msg[] = "cache entry has null sha1: %s";
+ static int allow = -1;
+
+ if (allow < 0)
+ allow = git_env_bool("GIT_ALLOW_NULL_SHA1", 0);
+ if (allow)
+ warning(msg, ce->name);
+ else
+ return error(msg, ce->name);
+ }
if (ce_write_entry(&c, newfd, ce, previous_name) < 0)
return -1;
}
}
struct ref_lock *lock_any_ref_for_update(const char *refname,
- const unsigned char *old_sha1, int flags)
+ const unsigned char *old_sha1,
+ int flags, int *type_p)
{
if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL))
return NULL;
- return lock_ref_sha1_basic(refname, old_sha1, flags, NULL);
+ return lock_ref_sha1_basic(refname, old_sha1, flags, type_p);
}
/*
return 0;
}
-static int repack_without_ref(const char *refname)
+static int repack_without_refs(const char **refnames, int n)
{
struct ref_dir *packed;
struct string_list refs_to_delete = STRING_LIST_INIT_DUP;
struct string_list_item *ref_to_delete;
+ int i, removed = 0;
+
+ /* Look for a packed ref */
+ for (i = 0; i < n; i++)
+ if (get_packed_ref(refnames[i]))
+ break;
- if (!get_packed_ref(refname))
- return 0; /* refname does not exist in packed refs */
+ /* Avoid locking if we have nothing to do */
+ if (i == n)
+ return 0; /* no refname exists in packed refs */
if (lock_packed_refs(0)) {
unable_to_lock_error(git_path("packed-refs"), errno);
- return error("cannot delete '%s' from packed refs", refname);
+ return error("cannot delete '%s' from packed refs", refnames[i]);
}
packed = get_packed_refs(&ref_cache);
- /* Remove refname from the cache: */
- if (remove_entry(packed, refname) == -1) {
+ /* Remove refnames from the cache */
+ for (i = 0; i < n; i++)
+ if (remove_entry(packed, refnames[i]) != -1)
+ removed = 1;
+ if (!removed) {
/*
- * The packed entry disappeared while we were
+ * All packed entries disappeared while we were
* acquiring the lock.
*/
rollback_packed_refs();
return 0;
}
- /* Remove any other accumulated cruft: */
+ /* Remove any other accumulated cruft */
do_for_each_entry_in_dir(packed, 0, curate_packed_ref_fn, &refs_to_delete);
for_each_string_list_item(ref_to_delete, &refs_to_delete) {
if (remove_entry(packed, ref_to_delete->string) == -1)
die("internal error");
}
- /* Write what remains: */
+ /* Write what remains */
return commit_packed_refs();
}
-int delete_ref(const char *refname, const unsigned char *sha1, int delopt)
+static int repack_without_ref(const char *refname)
{
- struct ref_lock *lock;
- int err, i = 0, ret = 0, flag = 0;
+ return repack_without_refs(&refname, 1);
+}
- lock = lock_ref_sha1_basic(refname, sha1, delopt, &flag);
- if (!lock)
- return 1;
+static int delete_ref_loose(struct ref_lock *lock, int flag)
+{
if (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) {
/* loose */
- i = strlen(lock->lk->filename) - 5; /* .lock */
+ int err, i = strlen(lock->lk->filename) - 5; /* .lock */
+
lock->lk->filename[i] = 0;
err = unlink_or_warn(lock->lk->filename);
- if (err && errno != ENOENT)
- ret = 1;
-
lock->lk->filename[i] = '.';
+ if (err && errno != ENOENT)
+ return 1;
}
+ return 0;
+}
+
+int delete_ref(const char *refname, const unsigned char *sha1, int delopt)
+{
+ struct ref_lock *lock;
+ int ret = 0, flag = 0;
+
+ lock = lock_ref_sha1_basic(refname, sha1, delopt, &flag);
+ if (!lock)
+ return 1;
+ ret |= delete_ref_loose(lock, flag);
+
/* removing the loose one could have resurrected an earlier
* packed one. Also, if it was not loose we need to repack
* without it.
return retval;
}
-int update_ref(const char *action, const char *refname,
- const unsigned char *sha1, const unsigned char *oldval,
- int flags, enum action_on_err onerr)
+static struct ref_lock *update_ref_lock(const char *refname,
+ const unsigned char *oldval,
+ int flags, int *type_p,
+ enum action_on_err onerr)
{
- static struct ref_lock *lock;
- lock = lock_any_ref_for_update(refname, oldval, flags);
+ struct ref_lock *lock;
+ lock = lock_any_ref_for_update(refname, oldval, flags, type_p);
if (!lock) {
const char *str = "Cannot lock the ref '%s'.";
switch (onerr) {
case DIE_ON_ERR: die(str, refname); break;
case QUIET_ON_ERR: break;
}
- return 1;
}
+ return lock;
+}
+
+static int update_ref_write(const char *action, const char *refname,
+ const unsigned char *sha1, struct ref_lock *lock,
+ enum action_on_err onerr)
+{
if (write_ref_sha1(lock, sha1, action) < 0) {
const char *str = "Cannot update the ref '%s'.";
switch (onerr) {
return 0;
}
-struct ref *find_ref_by_name(const struct ref *list, const char *name)
+int update_ref(const char *action, const char *refname,
+ const unsigned char *sha1, const unsigned char *oldval,
+ int flags, enum action_on_err onerr)
{
- for ( ; list; list = list->next)
- if (!strcmp(list->name, name))
- return (struct ref *)list;
- return NULL;
+ struct ref_lock *lock;
+ lock = update_ref_lock(refname, oldval, flags, 0, onerr);
+ if (!lock)
+ return 1;
+ return update_ref_write(action, refname, sha1, lock, onerr);
+}
+
+static int ref_update_compare(const void *r1, const void *r2)
+{
+ const struct ref_update * const *u1 = r1;
+ const struct ref_update * const *u2 = r2;
+ return strcmp((*u1)->ref_name, (*u2)->ref_name);
+}
+
+static int ref_update_reject_duplicates(struct ref_update **updates, int n,
+ enum action_on_err onerr)
+{
+ int i;
+ for (i = 1; i < n; i++)
+ if (!strcmp(updates[i - 1]->ref_name, updates[i]->ref_name)) {
+ const char *str =
+ "Multiple updates for ref '%s' not allowed.";
+ switch (onerr) {
+ case MSG_ON_ERR:
+ error(str, updates[i]->ref_name); break;
+ case DIE_ON_ERR:
+ die(str, updates[i]->ref_name); break;
+ case QUIET_ON_ERR:
+ break;
+ }
+ return 1;
+ }
+ return 0;
+}
+
+int update_refs(const char *action, const struct ref_update **updates_orig,
+ int n, enum action_on_err onerr)
+{
+ int ret = 0, delnum = 0, i;
+ struct ref_update **updates;
+ int *types;
+ struct ref_lock **locks;
+ const char **delnames;
+
+ if (!updates_orig || !n)
+ return 0;
+
+ /* Allocate work space */
+ updates = xmalloc(sizeof(*updates) * n);
+ types = xmalloc(sizeof(*types) * n);
+ locks = xcalloc(n, sizeof(*locks));
+ delnames = xmalloc(sizeof(*delnames) * n);
+
+ /* Copy, sort, and reject duplicate refs */
+ memcpy(updates, updates_orig, sizeof(*updates) * n);
+ qsort(updates, n, sizeof(*updates), ref_update_compare);
+ ret = ref_update_reject_duplicates(updates, n, onerr);
+ if (ret)
+ goto cleanup;
+
+ /* Acquire all locks while verifying old values */
+ for (i = 0; i < n; i++) {
+ locks[i] = update_ref_lock(updates[i]->ref_name,
+ (updates[i]->have_old ?
+ updates[i]->old_sha1 : NULL),
+ updates[i]->flags,
+ &types[i], onerr);
+ if (!locks[i]) {
+ ret = 1;
+ goto cleanup;
+ }
+ }
+
+ /* Perform updates first so live commits remain referenced */
+ for (i = 0; i < n; i++)
+ if (!is_null_sha1(updates[i]->new_sha1)) {
+ ret = update_ref_write(action,
+ updates[i]->ref_name,
+ updates[i]->new_sha1,
+ locks[i], onerr);
+ locks[i] = NULL; /* freed by update_ref_write */
+ if (ret)
+ goto cleanup;
+ }
+
+ /* Perform deletes now that updates are safely completed */
+ for (i = 0; i < n; i++)
+ if (locks[i]) {
+ delnames[delnum++] = locks[i]->ref_name;
+ ret |= delete_ref_loose(locks[i], types[i]);
+ }
+ ret |= repack_without_refs(delnames, delnum);
+ for (i = 0; i < delnum; i++)
+ unlink_or_warn(git_path("logs/%s", delnames[i]));
+ clear_loose_ref_cache(&ref_cache);
+
+cleanup:
+ for (i = 0; i < n; i++)
+ if (locks[i])
+ unlock_ref(locks[i]);
+ free(updates);
+ free(types);
+ free(locks);
+ free(delnames);
+ return ret;
}
/*
int force_write;
};
+/**
+ * Information needed for a single ref update. Set new_sha1 to the
+ * new value or to zero to delete the ref. To check the old value
+ * while locking the ref, set have_old to 1 and set old_sha1 to the
+ * value or to zero to ensure the ref does not exist before update.
+ */
+struct ref_update {
+ const char *ref_name;
+ unsigned char new_sha1[20];
+ unsigned char old_sha1[20];
+ int flags; /* REF_NODEREF? */
+ int have_old; /* 1 if old_sha1 is valid, 0 otherwise */
+};
+
/*
* Bit values set in the flags argument passed to each_ref_fn():
*/
#define REF_NODEREF 0x01
extern struct ref_lock *lock_any_ref_for_update(const char *refname,
const unsigned char *old_sha1,
- int flags);
+ int flags, int *type_p);
/** Close the file descriptor owned by a lock and return the status */
extern int close_ref(struct ref_lock *lock);
const unsigned char *sha1, const unsigned char *oldval,
int flags, enum action_on_err onerr);
+/**
+ * Lock all refs and then perform all modifications.
+ */
+int update_refs(const char *action, const struct ref_update **updates,
+ int n, enum action_on_err onerr);
+
extern int parse_hide_refs_config(const char *var, const char *value, const char *);
extern int ref_is_hidden(const char *);
#include "exec_cmd.h"
#include "run-command.h"
#include "pkt-line.h"
+#include "string-list.h"
#include "sideband.h"
#include "argv-array.h"
int verbosity;
unsigned long depth;
unsigned progress : 1,
+ check_self_contained_and_connected : 1,
followtags : 1,
dry_run : 1,
thin : 1;
};
static struct options options;
+static struct string_list cas_options = STRING_LIST_INIT_DUP;
static int set_option(const char *name, const char *value)
{
return -1;
return 0;
}
+ else if (!strcmp(name, "check-connectivity")) {
+ if (!strcmp(value, "true"))
+ options.check_self_contained_and_connected = 1;
+ else if (!strcmp(value, "false"))
+ options.check_self_contained_and_connected = 0;
+ else
+ return -1;
+ return 0;
+ }
+ else if (!strcmp(name, "cas")) {
+ struct strbuf val = STRBUF_INIT;
+ strbuf_addf(&val, "--" CAS_OPT_NAME "=%s", value);
+ string_list_append(&cas_options, val.buf);
+ strbuf_release(&val);
+ return 0;
+ }
else {
return 1 /* unsupported */;
}
struct strbuf preamble = STRBUF_INIT;
char *depth_arg = NULL;
int argc = 0, i, err;
- const char *argv[15];
+ const char *argv[16];
argv[argc++] = "fetch-pack";
argv[argc++] = "--stateless-rpc";
argv[argc++] = "-v";
argv[argc++] = "-v";
}
+ if (options.check_self_contained_and_connected)
+ argv[argc++] = "--check-self-contained-and-connected";
if (!options.progress)
argv[argc++] = "--no-progress";
if (options.depth) {
struct rpc_state rpc;
int i, err;
struct argv_array args;
+ struct string_list_item *cas_option;
argv_array_init(&args);
argv_array_pushl(&args, "send-pack", "--stateless-rpc", "--helper-status",
else if (options.verbosity > 1)
argv_array_push(&args, "--verbose");
argv_array_push(&args, options.progress ? "--progress" : "--no-progress");
+ for_each_string_list_item(cas_option, &cas_options)
+ argv_array_push(&args, cas_option->string);
argv_array_push(&args, url);
for (i = 0; i < nr_spec; i++)
argv_array_push(&args, specs[i]);
printf("fetch\n");
printf("option\n");
printf("push\n");
+ printf("check-connectivity\n");
printf("\n");
fflush(stdout);
} else {
}
ret = xcalloc(1, sizeof(struct remote));
+ ret->prune = -1; /* unspecified */
ALLOC_GROW(remotes, remotes_nr + 1, remotes_alloc);
remotes[remotes_nr++] = ret;
if (len)
remote->skip_default_update = git_config_bool(key, value);
else if (!strcmp(subkey, ".skipfetchall"))
remote->skip_default_update = git_config_bool(key, value);
+ else if (!strcmp(subkey, ".prune"))
+ remote->prune = git_config_bool(key, value);
else if (!strcmp(subkey, ".url")) {
const char *v;
if (git_config_string(&v, key, value))
free(sent_tips.tip);
}
+struct ref *find_ref_by_name(const struct ref *list, const char *name)
+{
+ for ( ; list; list = list->next)
+ if (!strcmp(list->name, name))
+ return (struct ref *)list;
+ return NULL;
+}
+
static void prepare_ref_index(struct string_list *ref_index, struct ref *ref)
{
for ( ; ref; ref = ref->next)
}
void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,
- int force_update)
+ int force_update)
{
struct ref *ref;
for (ref = remote_refs; ref; ref = ref->next) {
int force_ref_update = ref->force || force_update;
+ int reject_reason = 0;
if (ref->peer_ref)
hashcpy(ref->new_sha1, ref->peer_ref->new_sha1);
}
/*
+ * Bypass the usual "must fast-forward" check but
+ * replace it with a weaker "the old value must be
+ * this value we observed". If the remote ref has
+ * moved and is now different from what we expect,
+ * reject any push.
+ *
+ * It also is an error if the user told us to check
+ * with the remote-tracking branch to find the value
+ * to expect, but we did not have such a tracking
+ * branch.
+ */
+ if (ref->expect_old_sha1) {
+ if (ref->expect_old_no_trackback ||
+ hashcmp(ref->old_sha1, ref->old_sha1_expect))
+ reject_reason = REF_STATUS_REJECT_STALE;
+ }
+
+ /*
+ * The usual "must fast-forward" rules.
+ *
* Decide whether an individual refspec A:B can be
* pushed. The push will succeed if any of the
* following are true:
* passing the --force argument
*/
- if (!ref->deletion && !is_null_sha1(ref->old_sha1)) {
- int why = 0; /* why would this push require --force? */
-
+ else if (!ref->deletion && !is_null_sha1(ref->old_sha1)) {
if (!prefixcmp(ref->name, "refs/tags/"))
- why = REF_STATUS_REJECT_ALREADY_EXISTS;
+ reject_reason = REF_STATUS_REJECT_ALREADY_EXISTS;
else if (!has_sha1_file(ref->old_sha1))
- why = REF_STATUS_REJECT_FETCH_FIRST;
+ reject_reason = REF_STATUS_REJECT_FETCH_FIRST;
else if (!lookup_commit_reference_gently(ref->old_sha1, 1) ||
!lookup_commit_reference_gently(ref->new_sha1, 1))
- why = REF_STATUS_REJECT_NEEDS_FORCE;
+ reject_reason = REF_STATUS_REJECT_NEEDS_FORCE;
else if (!ref_newer(ref->new_sha1, ref->old_sha1))
- why = REF_STATUS_REJECT_NONFASTFORWARD;
-
- if (!force_ref_update)
- ref->status = why;
- else if (why)
- ref->forced_update = 1;
+ reject_reason = REF_STATUS_REJECT_NONFASTFORWARD;
}
+
+ /*
+ * "--force" will defeat any rejection implemented
+ * by the rules above.
+ */
+ if (!force_ref_update)
+ ref->status = reject_reason;
+ else if (reject_reason)
+ ref->forced_update = 1;
}
}
}
/*
- * Return true if there is anything to report, otherwise false.
+ * Compare a branch with its upstream, and save their differences (number
+ * of commits) in *num_ours and *num_theirs.
+ *
+ * Return 0 if branch has no upstream (no base), -1 if upstream is missing
+ * (with "gone" base), otherwise 1 (with base).
*/
int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs)
{
const char *rev_argv[10], *base;
int rev_argc;
- /*
- * Nothing to report unless we are marked to build on top of
- * somebody else.
- */
+ /* Cannot stat unless we are marked to build on top of somebody else. */
if (!branch ||
!branch->merge || !branch->merge[0] || !branch->merge[0]->dst)
return 0;
- /*
- * If what we used to build on no longer exists, there is
- * nothing to report.
- */
+ /* Cannot stat if what we used to build on no longer exists */
base = branch->merge[0]->dst;
if (read_ref(base, sha1))
- return 0;
+ return -1;
theirs = lookup_commit_reference(sha1);
if (!theirs)
- return 0;
+ return -1;
if (read_ref(branch->refname, sha1))
- return 0;
+ return -1;
ours = lookup_commit_reference(sha1);
if (!ours)
- return 0;
+ return -1;
/* are we the same? */
- if (theirs == ours)
- return 0;
+ if (theirs == ours) {
+ *num_theirs = *num_ours = 0;
+ return 1;
+ }
/* Run "rev-list --left-right ours...theirs" internally... */
rev_argc = 0;
*/
int format_tracking_info(struct branch *branch, struct strbuf *sb)
{
- int num_ours, num_theirs;
+ int ours, theirs;
const char *base;
+ int upstream_is_gone = 0;
- if (!stat_tracking_info(branch, &num_ours, &num_theirs))
+ switch (stat_tracking_info(branch, &ours, &theirs)) {
+ case 0:
+ /* no base */
return 0;
+ case -1:
+ /* with "gone" base */
+ upstream_is_gone = 1;
+ break;
+ default:
+ /* with base */
+ break;
+ }
base = branch->merge[0]->dst;
base = shorten_unambiguous_ref(base, 0);
- if (!num_theirs) {
+ if (upstream_is_gone) {
+ strbuf_addf(sb,
+ _("Your branch is based on '%s', but the upstream is gone.\n"),
+ base);
+ if (advice_status_hints)
+ strbuf_addf(sb,
+ _(" (use \"git branch --unset-upstream\" to fixup)\n"));
+ } else if (!ours && !theirs) {
+ strbuf_addf(sb,
+ _("Your branch is up-to-date with '%s'.\n"),
+ base);
+ } else if (!theirs) {
strbuf_addf(sb,
Q_("Your branch is ahead of '%s' by %d commit.\n",
"Your branch is ahead of '%s' by %d commits.\n",
- num_ours),
- base, num_ours);
+ ours),
+ base, ours);
if (advice_status_hints)
strbuf_addf(sb,
_(" (use \"git push\" to publish your local commits)\n"));
- } else if (!num_ours) {
+ } else if (!ours) {
strbuf_addf(sb,
Q_("Your branch is behind '%s' by %d commit, "
"and can be fast-forwarded.\n",
"Your branch is behind '%s' by %d commits, "
"and can be fast-forwarded.\n",
- num_theirs),
- base, num_theirs);
+ theirs),
+ base, theirs);
if (advice_status_hints)
strbuf_addf(sb,
_(" (use \"git pull\" to update your local branch)\n"));
"Your branch and '%s' have diverged,\n"
"and have %d and %d different commits each, "
"respectively.\n",
- num_theirs),
- base, num_ours, num_theirs);
+ theirs),
+ base, ours, theirs);
if (advice_status_hints)
strbuf_addf(sb,
_(" (use \"git pull\" to merge the remote branch into yours)\n"));
string_list_clear(&ref_names, 0);
return stale_refs;
}
+
+/*
+ * Compare-and-swap
+ */
+void clear_cas_option(struct push_cas_option *cas)
+{
+ int i;
+
+ for (i = 0; i < cas->nr; i++)
+ free(cas->entry[i].refname);
+ free(cas->entry);
+ memset(cas, 0, sizeof(*cas));
+}
+
+static struct push_cas *add_cas_entry(struct push_cas_option *cas,
+ const char *refname,
+ size_t refnamelen)
+{
+ struct push_cas *entry;
+ ALLOC_GROW(cas->entry, cas->nr + 1, cas->alloc);
+ entry = &cas->entry[cas->nr++];
+ memset(entry, 0, sizeof(*entry));
+ entry->refname = xmemdupz(refname, refnamelen);
+ return entry;
+}
+
+int parse_push_cas_option(struct push_cas_option *cas, const char *arg, int unset)
+{
+ const char *colon;
+ struct push_cas *entry;
+
+ if (unset) {
+ /* "--no-<option>" */
+ clear_cas_option(cas);
+ return 0;
+ }
+
+ if (!arg) {
+ /* just "--<option>" */
+ cas->use_tracking_for_rest = 1;
+ return 0;
+ }
+
+ /* "--<option>=refname" or "--<option>=refname:value" */
+ colon = strchrnul(arg, ':');
+ entry = add_cas_entry(cas, arg, colon - arg);
+ if (!*colon)
+ entry->use_tracking = 1;
+ else if (get_sha1(colon + 1, entry->expect))
+ return error("cannot parse expected object name '%s'", colon + 1);
+ return 0;
+}
+
+int parseopt_push_cas_option(const struct option *opt, const char *arg, int unset)
+{
+ return parse_push_cas_option(opt->value, arg, unset);
+}
+
+int is_empty_cas(const struct push_cas_option *cas)
+{
+ return !cas->use_tracking_for_rest && !cas->nr;
+}
+
+/*
+ * Look at remote.fetch refspec and see if we have a remote
+ * tracking branch for the refname there. Fill its current
+ * value in sha1[].
+ * If we cannot do so, return negative to signal an error.
+ */
+static int remote_tracking(struct remote *remote, const char *refname,
+ unsigned char sha1[20])
+{
+ char *dst;
+
+ dst = apply_refspecs(remote->fetch, remote->fetch_refspec_nr, refname);
+ if (!dst)
+ return -1; /* no tracking ref for refname at remote */
+ if (read_ref(dst, sha1))
+ return -1; /* we know what the tracking ref is but we cannot read it */
+ return 0;
+}
+
+static void apply_cas(struct push_cas_option *cas,
+ struct remote *remote,
+ struct ref *ref)
+{
+ int i;
+
+ /* Find an explicit --<option>=<name>[:<value>] entry */
+ for (i = 0; i < cas->nr; i++) {
+ struct push_cas *entry = &cas->entry[i];
+ if (!refname_match(entry->refname, ref->name, ref_rev_parse_rules))
+ continue;
+ ref->expect_old_sha1 = 1;
+ if (!entry->use_tracking)
+ hashcpy(ref->old_sha1_expect, cas->entry[i].expect);
+ else if (remote_tracking(remote, ref->name, ref->old_sha1_expect))
+ ref->expect_old_no_trackback = 1;
+ return;
+ }
+
+ /* Are we using "--<option>" to cover all? */
+ if (!cas->use_tracking_for_rest)
+ return;
+
+ ref->expect_old_sha1 = 1;
+ if (remote_tracking(remote, ref->name, ref->old_sha1_expect))
+ ref->expect_old_no_trackback = 1;
+}
+
+void apply_push_cas(struct push_cas_option *cas,
+ struct remote *remote,
+ struct ref *remote_refs)
+{
+ struct ref *ref;
+ for (ref = remote_refs; ref; ref = ref->next)
+ apply_cas(cas, remote, ref);
+}
#ifndef REMOTE_H
#define REMOTE_H
+#include "parse-options.h"
+
enum {
REMOTE_CONFIG,
REMOTE_REMOTES,
int fetch_tags;
int skip_default_update;
int mirror;
+ int prune;
const char *receivepack;
const char *uploadpack;
extern const struct refspec *tag_refspec;
+struct ref {
+ struct ref *next;
+ unsigned char old_sha1[20];
+ unsigned char new_sha1[20];
+ unsigned char old_sha1_expect[20]; /* used by expect-old */
+ char *symref;
+ unsigned int
+ force:1,
+ forced_update:1,
+ expect_old_sha1:1,
+ expect_old_no_trackback:1,
+ deletion:1,
+ matched:1;
+
+ /*
+ * Order is important here, as we write to FETCH_HEAD
+ * in numeric order. And the default NOT_FOR_MERGE
+ * should be 0, so that xcalloc'd structures get it
+ * by default.
+ */
+ enum {
+ FETCH_HEAD_MERGE = -1,
+ FETCH_HEAD_NOT_FOR_MERGE = 0,
+ FETCH_HEAD_IGNORE = 1
+ } fetch_head_status;
+
+ enum {
+ REF_STATUS_NONE = 0,
+ REF_STATUS_OK,
+ REF_STATUS_REJECT_NONFASTFORWARD,
+ REF_STATUS_REJECT_ALREADY_EXISTS,
+ REF_STATUS_REJECT_NODELETE,
+ REF_STATUS_REJECT_FETCH_FIRST,
+ REF_STATUS_REJECT_NEEDS_FORCE,
+ REF_STATUS_REJECT_STALE,
+ REF_STATUS_UPTODATE,
+ REF_STATUS_REMOTE_REJECT,
+ REF_STATUS_EXPECTING_REPORT
+ } status;
+ char *remote_status;
+ struct ref *peer_ref; /* when renaming */
+ char name[FLEX_ARRAY]; /* more */
+};
+
+#define REF_NORMAL (1u << 0)
+#define REF_HEADS (1u << 1)
+#define REF_TAGS (1u << 2)
+
+extern struct ref *find_ref_by_name(const struct ref *list, const char *name);
+
struct ref *alloc_ref(const char *name);
struct ref *copy_ref(const struct ref *ref);
struct ref *copy_ref_list(const struct ref *ref);
*/
void free_refs(struct ref *ref);
+struct extra_have_objects {
+ int nr, alloc;
+ unsigned char (*array)[20];
+};
+extern struct ref **get_remote_heads(int in, char *src_buf, size_t src_len,
+ struct ref **list, unsigned int flags,
+ struct extra_have_objects *);
+
int resolve_remote_symref(struct ref *ref, struct ref *list);
int ref_newer(const unsigned char *new_sha1, const unsigned char *old_sha1);
/* Return refs which no longer exist on remote */
struct ref *get_stale_heads(struct refspec *refs, int ref_count, struct ref *fetch_map);
+/*
+ * Compare-and-swap
+ */
+#define CAS_OPT_NAME "force-with-lease"
+
+struct push_cas_option {
+ unsigned use_tracking_for_rest:1;
+ struct push_cas {
+ unsigned char expect[20];
+ unsigned use_tracking:1;
+ char *refname;
+ } *entry;
+ int nr;
+ int alloc;
+};
+
+extern int parseopt_push_cas_option(const struct option *, const char *arg, int unset);
+extern int parse_push_cas_option(struct push_cas_option *, const char *arg, int unset);
+extern void clear_cas_option(struct push_cas_option *);
+
+extern int is_empty_cas(const struct push_cas_option *);
+void apply_push_cas(struct push_cas_option *, struct remote *, struct ref *);
+
#endif
#include "resolve-undo.h"
#include "ll-merge.h"
#include "attr.h"
+#include "pathspec.h"
#define RESOLVED 0
#define PUNTED 1
return 0;
}
-int rerere_forget(const char **pathspec)
+int rerere_forget(struct pathspec *pathspec)
{
int i, fd;
struct string_list conflict = STRING_LIST_INIT_DUP;
find_conflict(&conflict);
for (i = 0; i < conflict.nr; i++) {
struct string_list_item *it = &conflict.items[i];
- if (!match_pathspec(pathspec, it->string, strlen(it->string),
- 0, NULL))
+ if (!match_pathspec_depth(pathspec, it->string, strlen(it->string),
+ 0, NULL))
continue;
rerere_forget_one_path(it->string, &merge_rr);
}
#include "string-list.h"
+struct pathspec;
+
#define RERERE_AUTOUPDATE 01
#define RERERE_NOAUTOUPDATE 02
extern int setup_rerere(struct string_list *, int);
extern int rerere(int);
extern const char *rerere_path(const char *hex, const char *file);
-extern int rerere_forget(const char **);
+extern int rerere_forget(struct pathspec *);
extern int rerere_remaining(struct string_list *);
extern void rerere_clear(struct string_list *);
extern void rerere_gc(struct string_list *);
}
}
-void unmerge_index(struct index_state *istate, const char **pathspec)
+void unmerge_index(struct index_state *istate, const struct pathspec *pathspec)
{
int i;
for (i = 0; i < istate->cache_nr; i++) {
const struct cache_entry *ce = istate->cache[i];
- if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, NULL))
+ if (!match_pathspec_depth(pathspec, ce->name, ce_namelen(ce), 0, NULL))
continue;
i = unmerge_index_entry_at(istate, i);
}
extern struct string_list *resolve_undo_read(const char *, unsigned long);
extern void resolve_undo_clear_index(struct index_state *);
extern int unmerge_index_entry_at(struct index_state *, int);
-extern void unmerge_index(struct index_state *, const char **);
+extern void unmerge_index(struct index_state *, const struct pathspec *);
extern void unmerge_marked_index(struct index_state *);
#endif
#include "string-list.h"
#include "line-log.h"
#include "mailmap.h"
+#include "commit-slab.h"
volatile show_early_output_fn_t show_early_output;
* We don't care about the tree any more
* after it has been marked uninteresting.
*/
- free(tree->buffer);
- tree->buffer = NULL;
+ free_tree_buffer(tree);
}
void mark_parents_uninteresting(struct commit *commit)
i++;
}
free_pathspec(&revs->prune_data);
- init_pathspec(&revs->prune_data, prune);
+ parse_pathspec(&revs->prune_data, PATHSPEC_ALL_MAGIC, 0, "", prune);
revs->limited = 1;
}
*/
ALLOC_GROW(prune_data.path, prune_data.nr+1, prune_data.alloc);
prune_data.path[prune_data.nr++] = NULL;
- init_pathspec(&revs->prune_data,
- get_pathspec(revs->prefix, prune_data.path));
+ parse_pathspec(&revs->prune_data, 0, 0,
+ revs->prefix, prune_data.path);
}
if (revs->def == NULL)
revs->limited = 1;
if (revs->prune_data.nr) {
- diff_tree_setup_paths(revs->prune_data.raw, &revs->pruning);
+ copy_pathspec(&revs->pruning.pathspec, &revs->prune_data);
/* Can't prune commits with rename following: the paths change.. */
if (!DIFF_OPT_TST(&revs->diffopt, FOLLOW_RENAMES))
revs->prune = 1;
if (!revs->full_diff)
- diff_tree_setup_paths(revs->prune_data.raw, &revs->diffopt);
+ copy_pathspec(&revs->diffopt.pathspec,
+ &revs->prune_data);
}
if (revs->combine_merges)
revs->ignore_merges = 0;
return retval;
}
-static inline int want_ancestry(struct rev_info *revs)
+static inline int want_ancestry(const struct rev_info *revs)
{
return (revs->rewrite_parents || revs->children.name);
}
if (action == commit_show &&
!revs->show_all &&
revs->prune && revs->dense && want_ancestry(revs)) {
+ /*
+ * --full-diff on simplified parents is no good: it
+ * will show spurious changes from the commits that
+ * were elided. So we save the parents on the side
+ * when --full-diff is in effect.
+ */
+ if (revs->full_diff)
+ save_parents(revs, commit);
if (rewrite_parents(revs, commit, rewrite_one) < 0)
return commit_error;
}
free(entry);
if (revs->reflog_info) {
+ save_parents(revs, commit);
fake_reflog_parent(revs->reflog_info, commit);
commit->object.flags &= ~(ADDED | SEEN | SHOWN);
}
c = get_revision_internal(revs);
if (c && revs->graph)
graph_update(revs->graph, c);
+ if (!c)
+ free_saved_parents(revs);
return c;
}
fputs(mark, stdout);
putchar(' ');
}
+
+define_commit_slab(saved_parents, struct commit_list *);
+
+#define EMPTY_PARENT_LIST ((struct commit_list *)-1)
+
+void save_parents(struct rev_info *revs, struct commit *commit)
+{
+ struct commit_list **pp;
+
+ if (!revs->saved_parents_slab) {
+ revs->saved_parents_slab = xmalloc(sizeof(struct saved_parents));
+ init_saved_parents(revs->saved_parents_slab);
+ }
+
+ pp = saved_parents_at(revs->saved_parents_slab, commit);
+
+ /*
+ * When walking with reflogs, we may visit the same commit
+ * several times: once for each appearance in the reflog.
+ *
+ * In this case, save_parents() will be called multiple times.
+ * We want to keep only the first set of parents. We need to
+ * store a sentinel value for an empty (i.e., NULL) parent
+ * list to distinguish it from a not-yet-saved list, however.
+ */
+ if (*pp)
+ return;
+ if (commit->parents)
+ *pp = copy_commit_list(commit->parents);
+ else
+ *pp = EMPTY_PARENT_LIST;
+}
+
+struct commit_list *get_saved_parents(struct rev_info *revs, const struct commit *commit)
+{
+ struct commit_list *parents;
+
+ if (!revs->saved_parents_slab)
+ return commit->parents;
+
+ parents = *saved_parents_at(revs->saved_parents_slab, commit);
+ if (parents == EMPTY_PARENT_LIST)
+ return NULL;
+ return parents;
+}
+
+void free_saved_parents(struct rev_info *revs)
+{
+ if (revs->saved_parents_slab)
+ clear_saved_parents(revs->saved_parents_slab);
+}
struct rev_info;
struct log_info;
struct string_list;
+struct saved_parents;
struct rev_cmdline_info {
unsigned int nr;
/* line level range that we are chasing */
struct decoration line_log_data;
+
+ /* copies of the parent lists, for --full-diff display */
+ struct saved_parents *saved_parents_slab;
};
#define REV_TREE_SAME 0
extern int rewrite_parents(struct rev_info *revs, struct commit *commit,
rewrite_parent_fn_t rewrite_parent);
+
+/*
+ * Save a copy of the parent list, and return the saved copy. This is
+ * used by the log machinery to retrieve the original parents when
+ * commit->parents has been modified by history simpification.
+ *
+ * You may only call save_parents() once per commit (this is checked
+ * for non-root commits).
+ *
+ * get_saved_parents() will transparently return commit->parents if
+ * history simplification is off.
+ */
+extern void save_parents(struct rev_info *revs, struct commit *commit);
+extern struct commit_list *get_saved_parents(struct rev_info *revs, const struct commit *commit);
+extern void free_saved_parents(struct rev_info *revs);
+
#endif
#include "sideband.h"
#include "run-command.h"
#include "remote.h"
+#include "connect.h"
#include "send-pack.h"
#include "quote.h"
#include "transport.h"
case REF_STATUS_REJECT_ALREADY_EXISTS:
case REF_STATUS_REJECT_FETCH_FIRST:
case REF_STATUS_REJECT_NEEDS_FORCE:
+ case REF_STATUS_REJECT_STALE:
case REF_STATUS_UPTODATE:
continue;
default:
read_cache();
if (checkout_fast_forward(from, to, 1))
exit(1); /* the callee should have complained already */
- ref_lock = lock_any_ref_for_update("HEAD", unborn ? null_sha1 : from, 0);
+ ref_lock = lock_any_ref_for_update("HEAD", unborn ? null_sha1 : from,
+ 0, NULL);
strbuf_addf(&sb, "%s: fast-forward", action_name(opts));
ret = write_ref_sha1(ref_lock, to, sb.buf);
strbuf_release(&sb);
static int inside_git_dir = -1;
static int inside_work_tree = -1;
-static char *prefix_path_gently(const char *prefix, int len, const char *path)
+/*
+ * Normalize "path", prepending the "prefix" for relative paths. If
+ * remaining_prefix is not NULL, return the actual prefix still
+ * remains in the path. For example, prefix = sub1/sub2/ and path is
+ *
+ * foo -> sub1/sub2/foo (full prefix)
+ * ../foo -> sub1/foo (remaining prefix is sub1/)
+ * ../../bar -> bar (no remaining prefix)
+ * ../../sub1/sub2/foo -> sub1/sub2/foo (but no remaining prefix)
+ * `pwd`/../bar -> sub1/bar (no remaining prefix)
+ */
+char *prefix_path_gently(const char *prefix, int len,
+ int *remaining_prefix, const char *path)
{
const char *orig = path;
char *sanitized;
const char *temp = real_path(path);
sanitized = xmalloc(len + strlen(temp) + 1);
strcpy(sanitized, temp);
+ if (remaining_prefix)
+ *remaining_prefix = 0;
} else {
sanitized = xmalloc(len + strlen(path) + 1);
if (len)
memcpy(sanitized, prefix, len);
strcpy(sanitized + len, path);
+ if (remaining_prefix)
+ *remaining_prefix = len;
}
- if (normalize_path_copy(sanitized, sanitized))
+ if (normalize_path_copy_len(sanitized, sanitized, remaining_prefix))
goto error_out;
if (is_absolute_path(orig)) {
size_t root_len, len, total;
char *prefix_path(const char *prefix, int len, const char *path)
{
- char *r = prefix_path_gently(prefix, len, path);
+ char *r = prefix_path_gently(prefix, len, NULL, path);
if (!r)
die("'%s' is outside repository", path);
return r;
int path_inside_repo(const char *prefix, const char *path)
{
int len = prefix ? strlen(prefix) : 0;
- char *r = prefix_path_gently(prefix, len, path);
+ char *r = prefix_path_gently(prefix, len, NULL, path);
if (r) {
free(r);
return 1;
"'git <command> [<revision>...] -- [<file>...]'", arg);
}
-/*
- * Magic pathspec
- *
- * NEEDSWORK: These need to be moved to dir.h or even to a new
- * pathspec.h when we restructure get_pathspec() users to use the
- * "struct pathspec" interface.
- *
- * Possible future magic semantics include stuff like:
- *
- * { PATHSPEC_NOGLOB, '!', "noglob" },
- * { PATHSPEC_ICASE, '\0', "icase" },
- * { PATHSPEC_RECURSIVE, '*', "recursive" },
- * { PATHSPEC_REGEXP, '\0', "regexp" },
- *
- */
-#define PATHSPEC_FROMTOP (1<<0)
-
-static struct pathspec_magic {
- unsigned bit;
- char mnemonic; /* this cannot be ':'! */
- const char *name;
-} pathspec_magic[] = {
- { PATHSPEC_FROMTOP, '/', "top" },
-};
-
-/*
- * Take an element of a pathspec and check for magic signatures.
- * Append the result to the prefix.
- *
- * For now, we only parse the syntax and throw out anything other than
- * "top" magic.
- *
- * NEEDSWORK: This needs to be rewritten when we start migrating
- * get_pathspec() users to use the "struct pathspec" interface. For
- * example, a pathspec element may be marked as case-insensitive, but
- * the prefix part must always match literally, and a single stupid
- * string cannot express such a case.
- */
-static const char *prefix_pathspec(const char *prefix, int prefixlen, const char *elt)
-{
- unsigned magic = 0;
- const char *copyfrom = elt;
- int i;
-
- if (elt[0] != ':') {
- ; /* nothing to do */
- } else if (elt[1] == '(') {
- /* longhand */
- const char *nextat;
- for (copyfrom = elt + 2;
- *copyfrom && *copyfrom != ')';
- copyfrom = nextat) {
- size_t len = strcspn(copyfrom, ",)");
- if (copyfrom[len] == ',')
- nextat = copyfrom + len + 1;
- else
- /* handle ')' and '\0' */
- nextat = copyfrom + len;
- if (!len)
- continue;
- for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++)
- if (strlen(pathspec_magic[i].name) == len &&
- !strncmp(pathspec_magic[i].name, copyfrom, len)) {
- magic |= pathspec_magic[i].bit;
- break;
- }
- if (ARRAY_SIZE(pathspec_magic) <= i)
- die("Invalid pathspec magic '%.*s' in '%s'",
- (int) len, copyfrom, elt);
- }
- if (*copyfrom != ')')
- die("Missing ')' at the end of pathspec magic in '%s'", elt);
- copyfrom++;
- } else {
- /* shorthand */
- for (copyfrom = elt + 1;
- *copyfrom && *copyfrom != ':';
- copyfrom++) {
- char ch = *copyfrom;
-
- if (!is_pathspec_magic(ch))
- break;
- for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++)
- if (pathspec_magic[i].mnemonic == ch) {
- magic |= pathspec_magic[i].bit;
- break;
- }
- if (ARRAY_SIZE(pathspec_magic) <= i)
- die("Unimplemented pathspec magic '%c' in '%s'",
- ch, elt);
- }
- if (*copyfrom == ':')
- copyfrom++;
- }
-
- if (magic & PATHSPEC_FROMTOP)
- return xstrdup(copyfrom);
- else
- return prefix_path(prefix, prefixlen, copyfrom);
-}
-
-/*
- * N.B. get_pathspec() is deprecated in favor of the "struct pathspec"
- * based interface - see pathspec_magic above.
- *
- * Arguments:
- * - prefix - a path relative to the root of the working tree
- * - pathspec - a list of paths underneath the prefix path
- *
- * Iterates over pathspec, prepending each path with prefix,
- * and return the resulting list.
- *
- * If pathspec is empty, return a singleton list containing prefix.
- *
- * If pathspec and prefix are both empty, return an empty list.
- *
- * This is typically used by built-in commands such as add.c, in order
- * to normalize argv arguments provided to the built-in into a list of
- * paths to process, all relative to the root of the working tree.
- */
-const char **get_pathspec(const char *prefix, const char **pathspec)
-{
- const char *entry = *pathspec;
- const char **src, **dst;
- int prefixlen;
-
- if (!prefix && !entry)
- return NULL;
-
- if (!entry) {
- static const char *spec[2];
- spec[0] = prefix;
- spec[1] = NULL;
- return spec;
- }
-
- /* Otherwise we have to re-write the entries.. */
- src = pathspec;
- dst = pathspec;
- prefixlen = prefix ? strlen(prefix) : 0;
- while (*src) {
- *(dst++) = prefix_pathspec(prefix, prefixlen, *src);
- src++;
- }
- *dst = NULL;
- if (!*pathspec)
- return NULL;
- return pathspec;
-}
/*
* Test if it looks like we're at a git directory.
* byte 0 thru (ofs-1) are the same between
* lo and hi; ofs is the first byte that is
* different.
+ *
+ * If ofs==20, then no bytes are different,
+ * meaning we have entries with duplicate
+ * keys. We know that we are in a solid run
+ * of this entry (because the entries are
+ * sorted, and our lo and hi are the same,
+ * there can be nothing but this single key
+ * in between). So we can stop the search.
+ * Either one of these entries is it (and
+ * we do not care which), or we do not have
+ * it.
+ *
+ * Furthermore, we know that one of our
+ * endpoints must be the edge of the run of
+ * duplicates. For example, given this
+ * sequence:
+ *
+ * idx 0 1 2 3 4 5
+ * key A C C C C D
+ *
+ * If we are searching for "B", we might
+ * hit the duplicate run at lo=1, hi=3
+ * (e.g., by first mi=3, then mi=0). But we
+ * can never have lo > 1, because B < C.
+ * That is, if our key is less than the
+ * run, we know that "lo" is the edge, but
+ * we can say nothing of "hi". Similarly,
+ * if our key is greater than the run, we
+ * know that "hi" is the edge, but we can
+ * say nothing of "lo".
+ *
+ * Therefore if we do not find it, we also
+ * know where it would go if it did exist:
+ * just on the far side of the edge that we
+ * know about.
*/
+ if (ofs == 20) {
+ mi = lo;
+ mi_key = base + elem_size * mi + key_offset;
+ cmp = memcmp(mi_key, key, 20);
+ if (!cmp)
+ return mi;
+ if (cmp < 0)
+ return -1 - hi;
+ else
+ return -1 - lo;
+ }
+
hiv = hi_key[ofs_0];
if (ofs_0 < 19)
hiv = (hiv << 8) | hi_key[ofs_0+1];
if (find_pack_entry(sha1, &e))
return 1;
- return has_loose_object(sha1);
+ if (has_loose_object(sha1))
+ return 1;
+ reprepare_packed_git();
+ return find_pack_entry(sha1, &e);
}
static void check_tree(const void *buf, size_t size)
return -1;
sp++; /* beginning of type name, or closing brace for empty */
- if (!strncmp(commit_type, sp, 6) && sp[6] == '}')
+ if (!prefixcmp(sp, "commit}"))
expected_type = OBJ_COMMIT;
- else if (!strncmp(tree_type, sp, 4) && sp[4] == '}')
+ else if (!prefixcmp(sp, "tag}"))
+ expected_type = OBJ_TAG;
+ else if (!prefixcmp(sp, "tree}"))
expected_type = OBJ_TREE;
- else if (!strncmp(blob_type, sp, 4) && sp[4] == '}')
+ else if (!prefixcmp(sp, "blob}"))
expected_type = OBJ_BLOB;
else if (!prefixcmp(sp, "object}"))
expected_type = OBJ_ANY;
}
/*
- * Many callers know that the user meant to name a committish by
+ * Many callers know that the user meant to name a commit-ish by
* syntactical positions where the object name appears. Calling this
* function allows the machinery to disambiguate shorter-than-unique
- * abbreviated object names between committish and others.
+ * abbreviated object names between commit-ish and others.
*
* Note that this does NOT error out when the named object is not a
- * committish. It is merely to give a hint to the disambiguation
+ * commit-ish. It is merely to give a hint to the disambiguation
* machinery.
*/
int get_sha1_committish(const char *name, unsigned char *sha1)
#include "cache.h"
#include "commit.h"
#include "tag.h"
+#include "pkt-line.h"
static int is_shallow = -1;
static struct stat shallow_stat;
)
die("shallow file was changed during fetch");
}
+
+struct write_shallow_data {
+ struct strbuf *out;
+ int use_pack_protocol;
+ int count;
+};
+
+static int write_one_shallow(const struct commit_graft *graft, void *cb_data)
+{
+ struct write_shallow_data *data = cb_data;
+ const char *hex = sha1_to_hex(graft->sha1);
+ if (graft->nr_parent != -1)
+ return 0;
+ data->count++;
+ if (data->use_pack_protocol)
+ packet_buf_write(data->out, "shallow %s", hex);
+ else {
+ strbuf_addstr(data->out, hex);
+ strbuf_addch(data->out, '\n');
+ }
+ return 0;
+}
+
+int write_shallow_commits(struct strbuf *out, int use_pack_protocol)
+{
+ struct write_shallow_data data;
+ data.out = out;
+ data.use_pack_protocol = use_pack_protocol;
+ data.count = 0;
+ for_each_commit_graft(write_one_shallow, &data);
+ return data.count;
+}
+
+char *setup_temporary_shallow(void)
+{
+ struct strbuf sb = STRBUF_INIT;
+ int fd;
+
+ if (write_shallow_commits(&sb, 0)) {
+ struct strbuf path = STRBUF_INIT;
+ strbuf_addstr(&path, git_path("shallow_XXXXXX"));
+ fd = xmkstemp(path.buf);
+ if (write_in_full(fd, sb.buf, sb.len) != sb.len)
+ die_errno("failed to write to %s",
+ path.buf);
+ close(fd);
+ strbuf_release(&sb);
+ return strbuf_detach(&path, NULL);
+ }
+ /*
+ * is_repository_shallow() sees empty string as "no shallow
+ * file".
+ */
+ return xstrdup("");
+}
+
+void setup_alternate_shallow(struct lock_file *shallow_lock,
+ const char **alternate_shallow_file)
+{
+ struct strbuf sb = STRBUF_INIT;
+ int fd;
+
+ check_shallow_file_for_update();
+ fd = hold_lock_file_for_update(shallow_lock, git_path("shallow"),
+ LOCK_DIE_ON_ERROR);
+ if (write_shallow_commits(&sb, 0)) {
+ if (write_in_full(fd, sb.buf, sb.len) != sb.len)
+ die_errno("failed to write to %s",
+ shallow_lock->filename);
+ *alternate_shallow_file = shallow_lock->filename;
+ } else
+ /*
+ * is_repository_shallow() sees empty string as "no
+ * shallow file".
+ */
+ *alternate_shallow_file = "";
+ strbuf_release(&sb);
+}
#include "string-list.h"
#include "sha1-array.h"
#include "argv-array.h"
+#include "blob.h"
static struct string_list config_name_for_path;
static struct string_list config_fetch_recurse_submodules_for_name;
*/
static int gitmodules_is_unmerged;
+/*
+ * This flag is set if the .gitmodules file had unstaged modifications on
+ * startup. This must be checked before allowing modifications to the
+ * .gitmodules file with the intention to stage them later, because when
+ * continuing we would stage the modifications the user didn't stage herself
+ * too. That might change in a future version when we learn to stage the
+ * changes we do ourselves without staging any previous modifications.
+ */
+static int gitmodules_is_modified;
+
+
+int is_staging_gitmodules_ok(void)
+{
+ return !gitmodules_is_modified;
+}
+
+/*
+ * Try to update the "path" entry in the "submodule.<name>" section of the
+ * .gitmodules file. Return 0 only if a .gitmodules file was found, a section
+ * with the correct path=<oldpath> setting was found and we could update it.
+ */
+int update_path_in_gitmodules(const char *oldpath, const char *newpath)
+{
+ struct strbuf entry = STRBUF_INIT;
+ struct string_list_item *path_option;
+
+ if (!file_exists(".gitmodules")) /* Do nothing without .gitmodules */
+ return -1;
+
+ if (gitmodules_is_unmerged)
+ die(_("Cannot change unmerged .gitmodules, resolve merge conflicts first"));
+
+ path_option = unsorted_string_list_lookup(&config_name_for_path, oldpath);
+ if (!path_option) {
+ warning(_("Could not find section in .gitmodules where path=%s"), oldpath);
+ return -1;
+ }
+ strbuf_addstr(&entry, "submodule.");
+ strbuf_addstr(&entry, path_option->util);
+ strbuf_addstr(&entry, ".path");
+ if (git_config_set_in_file(".gitmodules", entry.buf, newpath) < 0) {
+ /* Maybe the user already did that, don't error out here */
+ warning(_("Could not update .gitmodules entry %s"), entry.buf);
+ strbuf_release(&entry);
+ return -1;
+ }
+ strbuf_release(&entry);
+ return 0;
+}
+
+/*
+ * Try to remove the "submodule.<name>" section from .gitmodules where the given
+ * path is configured. Return 0 only if a .gitmodules file was found, a section
+ * with the correct path=<path> setting was found and we could remove it.
+ */
+int remove_path_from_gitmodules(const char *path)
+{
+ struct strbuf sect = STRBUF_INIT;
+ struct string_list_item *path_option;
+
+ if (!file_exists(".gitmodules")) /* Do nothing without .gitmodules */
+ return -1;
+
+ if (gitmodules_is_unmerged)
+ die(_("Cannot change unmerged .gitmodules, resolve merge conflicts first"));
+
+ path_option = unsorted_string_list_lookup(&config_name_for_path, path);
+ if (!path_option) {
+ warning(_("Could not find section in .gitmodules where path=%s"), path);
+ return -1;
+ }
+ strbuf_addstr(§, "submodule.");
+ strbuf_addstr(§, path_option->util);
+ if (git_config_rename_section_in_file(".gitmodules", sect.buf, NULL) < 0) {
+ /* Maybe the user already did that, don't error out here */
+ warning(_("Could not remove .gitmodules entry for %s"), path);
+ strbuf_release(§);
+ return -1;
+ }
+ strbuf_release(§);
+ return 0;
+}
+
+void stage_updated_gitmodules(void)
+{
+ struct strbuf buf = STRBUF_INIT;
+ struct stat st;
+ int pos;
+ struct cache_entry *ce;
+ int namelen = strlen(".gitmodules");
+
+ pos = cache_name_pos(".gitmodules", namelen);
+ if (pos < 0) {
+ warning(_("could not find .gitmodules in index"));
+ return;
+ }
+ ce = active_cache[pos];
+ ce->ce_flags = namelen;
+ if (strbuf_read_file(&buf, ".gitmodules", 0) < 0)
+ die(_("reading updated .gitmodules failed"));
+ if (lstat(".gitmodules", &st) < 0)
+ die_errno(_("unable to stat updated .gitmodules"));
+ fill_stat_cache_info(ce, &st);
+ ce->ce_mode = ce_mode_from_stat(ce, st.st_mode);
+ if (remove_cache_entry_at(pos) < 0)
+ die(_("unable to remove .gitmodules from index"));
+ if (write_sha1_file(buf.buf, buf.len, blob_type, ce->sha1))
+ die(_("adding updated .gitmodules failed"));
+ if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE))
+ die(_("staging updated .gitmodules failed"));
+}
+
static int add_submodule_odb(const char *path)
{
struct strbuf objects_directory = STRBUF_INIT;
!memcmp(ce->name, ".gitmodules", 11))
gitmodules_is_unmerged = 1;
}
+ } else if (pos < active_nr) {
+ struct stat st;
+ if (lstat(".gitmodules", &st) == 0 &&
+ ce_match_stat(active_cache[pos], &st, 0) & DATA_CHANGED)
+ gitmodules_is_modified = 1;
}
if (!gitmodules_is_unmerged)
free(merges.objects);
return 0;
}
+
+/* Update gitfile and core.worktree setting to connect work tree and git dir */
+void connect_work_tree_and_git_dir(const char *work_tree, const char *git_dir)
+{
+ struct strbuf file_name = STRBUF_INIT;
+ struct strbuf rel_path = STRBUF_INIT;
+ const char *real_work_tree = xstrdup(real_path(work_tree));
+ FILE *fp;
+
+ /* Update gitfile */
+ strbuf_addf(&file_name, "%s/.git", work_tree);
+ fp = fopen(file_name.buf, "w");
+ if (!fp)
+ die(_("Could not create git link %s"), file_name.buf);
+ fprintf(fp, "gitdir: %s\n", relative_path(git_dir, real_work_tree,
+ &rel_path));
+ fclose(fp);
+
+ /* Update core.worktree setting */
+ strbuf_reset(&file_name);
+ strbuf_addf(&file_name, "%s/config", git_dir);
+ if (git_config_set_in_file(file_name.buf, "core.worktree",
+ relative_path(real_work_tree, git_dir,
+ &rel_path)))
+ die(_("Could not set core.worktree in %s"),
+ file_name.buf);
+
+ strbuf_release(&file_name);
+ strbuf_release(&rel_path);
+ free((void *)real_work_tree);
+}
RECURSE_SUBMODULES_ON = 2
};
+int is_staging_gitmodules_ok(void);
+int update_path_in_gitmodules(const char *oldpath, const char *newpath);
+int remove_path_from_gitmodules(const char *path);
+void stage_updated_gitmodules(void);
void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt,
const char *path);
int submodule_config(const char *var, const char *value, void *cb);
int find_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_name,
struct string_list *needs_pushing);
int push_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_name);
+void connect_work_tree_and_git_dir(const char *work_tree, const char *git_dir);
#endif
t[0-9][0-9][0-9][0-9]/* -whitespace
+t0110/url-* binary
check_count -L6,3 B 1 B1 1 B2 1 D 1
'
+test_expect_success 'blame -L -X' '
+ test_must_fail $PROG -L-1 file
+'
+
+test_expect_success 'blame -L 0' '
+ test_must_fail $PROG -L0 file
+'
+
+test_expect_success 'blame -L ,0' '
+ test_must_fail $PROG -L,0 file
+'
+
+test_expect_success 'blame -L ,+0' '
+ test_must_fail $PROG -L,+0 file
+'
+
+test_expect_success 'blame -L X,+0' '
+ test_must_fail $PROG -L1,+0 file
+'
+
test_expect_success 'blame -L X,+1' '
check_count -L3,+1 B2 1
'
check_count -L3,+4 B 1 B1 1 B2 1 D 1
'
+test_expect_success 'blame -L ,-0' '
+ test_must_fail $PROG -L,-0 file
+'
+
+test_expect_success 'blame -L X,-0' '
+ test_must_fail $PROG -L1,-0 file
+'
+
test_expect_success 'blame -L X,-1' '
check_count -L3,-1 B2 1
'
check_count -L/99/,-3 B 1 B2 1 D 1
'
+# 'file' ends with an incomplete line, so 'wc' reports one fewer lines than
+# git-blame sees, hence the last line is actually $(wc...)+1.
+test_expect_success 'blame -L X (X == nlines)' '
+ n=$(expr $(wc -l <file) + 1) &&
+ check_count -L$n C 1
+'
+
+test_expect_success 'blame -L X (X == nlines + 1)' '
+ n=$(expr $(wc -l <file) + 2) &&
+ test_must_fail $PROG -L$n file
+'
+
test_expect_success 'blame -L X (X > nlines)' '
test_must_fail $PROG -L12345 file
'
+test_expect_success 'blame -L ,Y (Y == nlines)' '
+ n=$(expr $(wc -l <file) + 1) &&
+ check_count -L,$n A 1 B 1 B1 1 B2 1 "A U Thor" 1 C 1 D 1 E 1
+'
+
+test_expect_success 'blame -L ,Y (Y == nlines + 1)' '
+ n=$(expr $(wc -l <file) + 2) &&
+ test_must_fail $PROG -L,$n file
+'
+
test_expect_success 'blame -L ,Y (Y > nlines)' '
test_must_fail $PROG -L,12345 file
'
+test_expect_success 'blame -L multiple (disjoint)' '
+ check_count -L2,3 -L6,7 A 1 B1 1 B2 1 "A U Thor" 1
+'
+
+test_expect_success 'blame -L multiple (disjoint: unordered)' '
+ check_count -L6,7 -L2,3 A 1 B1 1 B2 1 "A U Thor" 1
+'
+
+test_expect_success 'blame -L multiple (adjacent)' '
+ check_count -L2,3 -L4,5 A 1 B 1 B2 1 D 1
+'
+
+test_expect_success 'blame -L multiple (adjacent: unordered)' '
+ check_count -L4,5 -L2,3 A 1 B 1 B2 1 D 1
+'
+
+test_expect_success 'blame -L multiple (overlapping)' '
+ check_count -L2,4 -L3,5 A 1 B 1 B2 1 D 1
+'
+
+test_expect_success 'blame -L multiple (overlapping: unordered)' '
+ check_count -L3,5 -L2,4 A 1 B 1 B2 1 D 1
+'
+
+test_expect_success 'blame -L multiple (superset/subset)' '
+ check_count -L2,8 -L3,5 A 1 B 1 B1 1 B2 1 C 1 D 1 "A U Thor" 1
+'
+
+test_expect_success 'blame -L multiple (superset/subset: unordered)' '
+ check_count -L3,5 -L2,8 A 1 B 1 B1 1 B2 1 C 1 D 1 "A U Thor" 1
+'
+
+test_expect_success 'blame -L /RE/ (relative)' '
+ check_count -L3,3 -L/fox/ B1 1 B2 1 C 1 D 1 "A U Thor" 1
+'
+
+test_expect_success 'blame -L /RE/ (relative: no preceding range)' '
+ check_count -L/dog/ A 1 B 1 B1 1 B2 1 C 1 D 1 "A U Thor" 1
+'
+
+test_expect_success 'blame -L /RE/ (relative: adjacent)' '
+ check_count -L1,1 -L/dog/,+1 A 1 E 1
+'
+
+test_expect_success 'blame -L /RE/ (relative: not found)' '
+ test_must_fail $PROG -L4,4 -L/dog/ file
+'
+
+test_expect_success 'blame -L /RE/ (relative: end-of-file)' '
+ test_must_fail $PROG -L, -L/$/ file
+'
+
+test_expect_success 'blame -L ^/RE/ (absolute)' '
+ check_count -L3,3 -L^/dog/,+2 A 1 B2 1
+'
+
+test_expect_success 'blame -L ^/RE/ (absolute: no preceding range)' '
+ check_count -L^/dog/,+2 A 1 B2 1
+'
+
+test_expect_success 'blame -L ^/RE/ (absolute: not found)' '
+ test_must_fail $PROG -L4,4 -L^/tambourine/ file
+'
+
+test_expect_success 'blame -L ^/RE/ (absolute: end-of-file)' '
+ n=$(expr $(wc -l <file) + 1) &&
+ check_count -L$n -L^/$/,+2 A 1 C 1 E 1
+'
+
test_expect_success 'setup -L :regex' '
tr Q "\\t" >hello.c <<-\EOF &&
int main(int argc, const char *argv[])
test_must_fail $PROG -L:nomatch hello.c
'
-test_expect_success 'blame -L bogus' '
- test_must_fail $PROG -L file &&
- test_must_fail $PROG -L1,+ file &&
- test_must_fail $PROG -L1,- file &&
- test_must_fail $PROG -LX file &&
- test_must_fail $PROG -L1,X file &&
- test_must_fail $PROG -L1,+N file &&
+test_expect_success 'blame -L :RE (relative)' '
+ check_count -f hello.c -L3,3 -L:ma.. F 1 H 4
+'
+
+test_expect_success 'blame -L :RE (relative: no preceding range)' '
+ check_count -f hello.c -L:ma.. F 4 G 1
+'
+
+test_expect_success 'blame -L :RE (relative: not found)' '
+ test_must_fail $PROG -L3,3 -L:tambourine hello.c
+'
+
+test_expect_success 'blame -L :RE (relative: end-of-file)' '
+ test_must_fail $PROG -L, -L:main hello.c
+'
+
+test_expect_success 'blame -L ^:RE (absolute)' '
+ check_count -f hello.c -L3,3 -L^:ma.. F 4 G 1
+'
+
+test_expect_success 'blame -L ^:RE (absolute: no preceding range)' '
+ check_count -f hello.c -L^:ma.. F 4 G 1
+'
+
+test_expect_success 'blame -L ^:RE (absolute: not found)' '
+ test_must_fail $PROG -L4,4 -L^:tambourine hello.c
+'
+
+test_expect_success 'blame -L ^:RE (absolute: end-of-file)' '
+ n=$(printf "%d" $(wc -l <hello.c)) &&
+ check_count -f hello.c -L$n -L^:ma.. F 4 G 1 H 1
+'
+
+test_expect_success 'setup incremental' '
+ (
+ GIT_AUTHOR_NAME=I &&
+ export GIT_AUTHOR_NAME &&
+ GIT_AUTHOR_EMAIL=I@test.git &&
+ export GIT_AUTHOR_EMAIL &&
+ >incremental &&
+ git add incremental &&
+ git commit -m "step 0" &&
+ printf "partial" >>incremental &&
+ git commit -a -m "step 0.5" &&
+ echo >>incremental &&
+ git commit -a -m "step 1"
+ )
+'
+
+test_expect_success 'blame empty' '
+ check_count -h HEAD^^ -f incremental
+'
+
+test_expect_success 'blame -L 0 empty' '
+ test_must_fail $PROG -L0 incremental HEAD^^
+'
+
+test_expect_success 'blame -L 1 empty' '
+ test_must_fail $PROG -L1 incremental HEAD^^
+'
+
+test_expect_success 'blame -L 2 empty' '
+ test_must_fail $PROG -L2 incremental HEAD^^
+'
+
+test_expect_success 'blame half' '
+ check_count -h HEAD^ -f incremental I 1
+'
+
+test_expect_success 'blame -L 0 half' '
+ test_must_fail $PROG -L0 incremental HEAD^
+'
+
+test_expect_success 'blame -L 1 half' '
+ check_count -h HEAD^ -f incremental -L1 I 1
+'
+
+test_expect_success 'blame -L 2 half' '
+ test_must_fail $PROG -L2 incremental HEAD^
+'
+
+test_expect_success 'blame -L 3 half' '
+ test_must_fail $PROG -L3 incremental HEAD^
+'
+
+test_expect_success 'blame full' '
+ check_count -f incremental I 1
+'
+
+test_expect_success 'blame -L 0 full' '
+ test_must_fail $PROG -L0 incremental
+'
+
+test_expect_success 'blame -L 1 full' '
+ check_count -f incremental -L1 I 1
+'
+
+test_expect_success 'blame -L 2 full' '
+ test_must_fail $PROG -L2 incremental
+'
+
+test_expect_success 'blame -L 3 full' '
+ test_must_fail $PROG -L3 incremental
+'
+
+test_expect_success 'blame -L' '
+ test_must_fail $PROG -L file
+'
+
+test_expect_success 'blame -L X,+' '
+ test_must_fail $PROG -L1,+ file
+'
+
+test_expect_success 'blame -L X,-' '
+ test_must_fail $PROG -L1,- file
+'
+
+test_expect_success 'blame -L X (non-numeric X)' '
+ test_must_fail $PROG -LX file
+'
+
+test_expect_success 'blame -L X,Y (non-numeric Y)' '
+ test_must_fail $PROG -L1,Y file
+'
+
+test_expect_success 'blame -L X,+N (non-numeric N)' '
+ test_must_fail $PROG -L1,+N file
+'
+
+test_expect_success 'blame -L X,-N (non-numeric N)' '
test_must_fail $PROG -L1,-N file
'
+
+test_expect_success 'blame -L ,^/RE/' '
+ test_must_fail $PROG -L1,^/99/ file
+'
P4PORT=localhost:$P4DPORT
P4CLIENT=client
P4EDITOR=:
-export P4PORT P4CLIENT P4EDITOR
+unset P4CHARSET
+export P4PORT P4CLIENT P4EDITOR P4CHARSET
db="$TRASH_DIRECTORY/db"
cli="$TRASH_DIRECTORY/cli"
-f "$TEST_PATH/apache.conf" $HTTPD_PARA -k stop
}
-test_http_push_nonff() {
+test_http_push_nonff () {
REMOTE_REPO=$1
LOCAL_REPO=$2
BRANCH=$3
+ EXPECT_CAS_RESULT=${4-failure}
test_expect_success 'non-fast-forward push fails' '
cd "$REMOTE_REPO" &&
test_expect_success 'non-fast-forward push shows help message' '
test_i18ngrep "Updates were rejected because" output
'
+
+ test_expect_failure 'force with lease aka cas' '
+ HEAD=$( cd "$REMOTE_REPO" && git rev-parse --verify HEAD ) &&
+ test_when_finished '\''
+ (cd "$REMOTE_REPO" && git update-ref HEAD "$HEAD")
+ '\'' &&
+ (
+ cd "$LOCAL_REPO" &&
+ git push -v --force-with-lease=$BRANCH:$HEAD origin
+ ) &&
+ git rev-parse --verify "$BRANCH" >expect &&
+ (
+ cd "$REMOTE_REPO" && git rev-parse --verify HEAD
+ ) >actual &&
+ test_cmp expect actual
+ '
}
setup_askpass_helper() {
<IfModule !mod_version.c>
LoadModule version_module modules/mod_version.so
</IfModule>
+<IfModule !mod_headers.c>
+ LoadModule headers_module modules/mod_headers.so
+</IfModule>
<IfVersion < 2.4>
LockFile accept.lock
SetEnv GIT_HTTP_EXPORT_ALL
SetEnv GIT_NAMESPACE ns
</LocationMatch>
+<LocationMatch /smart_cookies/>
+ SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH}
+ SetEnv GIT_HTTP_EXPORT_ALL
+ Header set Set-Cookie name=value
+</LocationMatch>
ScriptAliasMatch /smart_*[^/]*/(.*) ${GIT_EXEC_PATH}/git-http-backend/$1
ScriptAlias /broken_smart/ broken-smart-http.sh/
<Directory ${GIT_EXEC_PATH}>
--- /dev/null
+#!/bin/sh
+#
+# Support routines for hand-crafting weird or malicious packs.
+#
+# You can make a complete pack like:
+#
+# pack_header 2 >foo.pack &&
+# pack_obj e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 >>foo.pack &&
+# pack_obj e68fe8129b546b101aee9510c5328e7f21ca1d18 >>foo.pack &&
+# pack_trailer foo.pack
+
+# Print the big-endian 4-byte octal representation of $1
+uint32_octal () {
+ n=$1
+ printf '\%o' $(($n / 16777216)); n=$((n % 16777216))
+ printf '\%o' $(($n / 65536)); n=$((n % 65536))
+ printf '\%o' $(($n / 256)); n=$((n % 256))
+ printf '\%o' $(($n ));
+}
+
+# Print the big-endian 4-byte binary representation of $1
+uint32_binary () {
+ printf "$(uint32_octal "$1")"
+}
+
+# Print a pack header, version 2, for a pack with $1 objects
+pack_header () {
+ printf 'PACK' &&
+ printf '\0\0\0\2' &&
+ uint32_binary "$1"
+}
+
+# Print the pack data for object $1, as a delta against object $2 (or as a full
+# object if $2 is missing or empty). The output is suitable for including
+# directly in the packfile, and represents the entirety of the object entry.
+# Doing this on the fly (especially picking your deltas) is quite tricky, so we
+# have hardcoded some well-known objects. See the case statements below for the
+# complete list.
+pack_obj () {
+ case "$1" in
+ # empty blob
+ e69de29bb2d1d6434b8b29ae775ad8c2e48c5391)
+ case "$2" in
+ '')
+ printf '\060\170\234\003\0\0\0\0\1'
+ return
+ ;;
+ esac
+ ;;
+
+ # blob containing "\7\76"
+ e68fe8129b546b101aee9510c5328e7f21ca1d18)
+ case "$2" in
+ '')
+ printf '\062\170\234\143\267\3\0\0\116\0\106'
+ return
+ ;;
+ 01d7713666f4de822776c7622c10f1b07de280dc)
+ printf '\165\1\327\161\66\146\364\336\202\47\166' &&
+ printf '\307\142\54\20\361\260\175\342\200\334\170' &&
+ printf '\234\143\142\142\142\267\003\0\0\151\0\114'
+ return
+ ;;
+ esac
+ ;;
+
+ # blob containing "\7\0"
+ 01d7713666f4de822776c7622c10f1b07de280dc)
+ case "$2" in
+ '')
+ printf '\062\170\234\143\147\0\0\0\20\0\10'
+ return
+ ;;
+ e68fe8129b546b101aee9510c5328e7f21ca1d18)
+ printf '\165\346\217\350\22\233\124\153\20\32\356' &&
+ printf '\225\20\305\62\216\177\41\312\35\30\170\234' &&
+ printf '\143\142\142\142\147\0\0\0\53\0\16'
+ return
+ ;;
+ esac
+ ;;
+ esac
+
+ echo >&2 "BUG: don't know how to print $1${2:+ (from $2)}"
+ return 1
+}
+
+# Compute and append pack trailer to "$1"
+pack_trailer () {
+ test-sha1 -b <"$1" >trailer.tmp &&
+ cat trailer.tmp >>"$1" &&
+ rm -f trailer.tmp
+}
+
+# Remove any existing packs to make sure that
+# whatever we index next will be the pack that we
+# actually use.
+clear_packs () {
+ rm -f .git/objects/pack/*
+}
test -d realgitdir/refs
'
+test_expect_success 're-init on .git file' '
+ ( cd newdir && git init )
+'
+
test_expect_success 're-init to update git link' '
(
cd newdir &&
test_expect_success_multi SYMLINKS 'beyond a symlink' '' '
test_check_ignore "a/symlink/foo" 128 &&
- test_stderr "fatal: '\''a/symlink/foo'\'' is beyond a symbolic link"
+ test_stderr "fatal: pathspec '\''a/symlink/foo'\'' is beyond a symbolic link"
'
test_expect_success_multi SYMLINKS 'beyond a symlink from subdirectory' '' '
cd a &&
test_check_ignore "symlink/foo" 128
) &&
- test_stderr "fatal: '\''symlink/foo'\'' is beyond a symbolic link"
+ test_stderr "fatal: pathspec '\''symlink/foo'\'' is beyond a symbolic link"
'
############################################################################
test_expect_success_multi 'submodule' '' '
test_check_ignore "a/submodule/one" 128 &&
- test_stderr "fatal: Path '\''a/submodule/one'\'' is in submodule '\''a/submodule'\''"
+ test_stderr "fatal: Pathspec '\''a/submodule/one'\'' is in submodule '\''a/submodule'\''"
'
test_expect_success_multi 'submodule from subdirectory' '' '
cd a &&
test_check_ignore "submodule/one" 128
) &&
- test_stderr "fatal: Path '\''a/submodule/one'\'' is in submodule '\''a/submodule'\''"
+ test_stderr "fatal: Pathspec '\''submodule/one'\'' is in submodule '\''a/submodule'\''"
'
############################################################################
test_must_fail git add test.fc
'
+test -n "$GIT_TEST_LONG" && test_set_prereq EXPENSIVE
+
+test_expect_success EXPENSIVE 'filter large file' '
+ git config filter.largefile.smudge cat &&
+ git config filter.largefile.clean cat &&
+ for i in $(test_seq 1 2048); do printf "%1048576d" 1; done >2GB &&
+ echo "2GB filter=largefile" >.gitattributes &&
+ git add 2GB 2>err &&
+ ! test -s err &&
+ rm -f 2GB &&
+ git checkout -- 2GB 2>err &&
+ ! test -s err
+'
+
test_done
test_expect_success "setup unicode normalization tests" '
test_create_repo unicode &&
cd unicode &&
+ git config core.precomposeunicode false &&
touch "$aumlcdiar" &&
git add "$aumlcdiar" &&
git commit -m initial &&
--- /dev/null
+#!/bin/sh
+
+test_description='"-C <path>" option and its effects on other path-related options'
+
+. ./test-lib.sh
+
+test_expect_success '"git -C <path>" runs git from the directory <path>' '
+ test_create_repo dir1 &&
+ echo 1 >dir1/a.txt &&
+ msg="initial in dir1" &&
+ (cd dir1 && git add a.txt && git commit -m "$msg") &&
+ echo "$msg" >expected &&
+ git -C dir1 log --format=%s >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'Multiple -C options: "-C dir1 -C dir2" is equivalent to "-C dir1/dir2"' '
+ test_create_repo dir1/dir2 &&
+ echo 1 >dir1/dir2/b.txt &&
+ git -C dir1/dir2 add b.txt &&
+ msg="initial in dir1/dir2" &&
+ echo "$msg" >expected &&
+ git -C dir1/dir2 commit -m "$msg" &&
+ git -C dir1 -C dir2 log --format=%s >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'Effect on --git-dir option: "-C c --git-dir=a.git" is equivalent to "--git-dir c/a.git"' '
+ mkdir c &&
+ mkdir c/a &&
+ mkdir c/a.git &&
+ (cd c/a.git && git init --bare) &&
+ echo 1 >c/a/a.txt &&
+ git --git-dir c/a.git --work-tree=c/a add a.txt &&
+ git --git-dir c/a.git --work-tree=c/a commit -m "initial" &&
+ git --git-dir=c/a.git log -1 --format=%s >expected &&
+ git -C c --git-dir=a.git log -1 --format=%s >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'Order should not matter: "--git-dir=a.git -C c" is equivalent to "-C c --git-dir=a.git"' '
+ git -C c --git-dir=a.git log -1 --format=%s >expected &&
+ git --git-dir=a.git -C c log -1 --format=%s >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'Effect on --work-tree option: "-C c/a.git --work-tree=../a" is equivalent to "--work-tree=c/a --git-dir=c/a.git"' '
+ rm c/a/a.txt &&
+ git --git-dir=c/a.git --work-tree=c/a status >expected &&
+ git -C c/a.git --work-tree=../a status >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'Order should not matter: "--work-tree=../a -C c/a.git" is equivalent to "-C c/a.git --work-tree=../a"' '
+ git -C c/a.git --work-tree=../a status >expected &&
+ git --work-tree=../a -C c/a.git status >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'Effect on --git-dir and --work-tree options - "-C c --git-dir=a.git --work-tree=a" is equivalent to "--git-dir=c/a.git --work-tree=c/a"' '
+ git --git-dir=c/a.git --work-tree=c/a status >expected &&
+ git -C c --git-dir=a.git --work-tree=a status >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'Order should not matter: "-C c --git-dir=a.git --work-tree=a" is equivalent to "--git-dir=a.git -C c --work-tree=a"' '
+ git -C c --git-dir=a.git --work-tree=a status >expected &&
+ git --git-dir=a.git -C c --work-tree=a status >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'Order should not matter: "-C c --git-dir=a.git --work-tree=a" is equivalent to "--git-dir=a.git --work-tree=a -C c"' '
+ git -C c --git-dir=a.git --work-tree=a status >expected &&
+ git --git-dir=a.git --work-tree=a -C c status >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'Relative followed by fullpath: "-C ./here -C /there" is equivalent to "-C /there"' '
+ echo "initial in dir1/dir2" >expected &&
+ git -C dir1 -C "$(pwd)/dir1/dir2" log --format=%s >actual &&
+ test_cmp expected actual
+'
+
+test_done
--- /dev/null
+#!/bin/sh
+
+test_description='urlmatch URL normalization'
+. ./test-lib.sh
+
+# The base name of the test url files
+tu="$TEST_DIRECTORY/t0110/url"
+
+# Note that only file: URLs should be allowed without a host
+
+test_expect_success 'url scheme' '
+ ! test-urlmatch-normalization "" &&
+ ! test-urlmatch-normalization "_" &&
+ ! test-urlmatch-normalization "scheme" &&
+ ! test-urlmatch-normalization "scheme:" &&
+ ! test-urlmatch-normalization "scheme:/" &&
+ ! test-urlmatch-normalization "scheme://" &&
+ ! test-urlmatch-normalization "file" &&
+ ! test-urlmatch-normalization "file:" &&
+ ! test-urlmatch-normalization "file:/" &&
+ test-urlmatch-normalization "file://" &&
+ ! test-urlmatch-normalization "://acme.co" &&
+ ! test-urlmatch-normalization "x_test://acme.co" &&
+ ! test-urlmatch-normalization "-test://acme.co" &&
+ ! test-urlmatch-normalization "0test://acme.co" &&
+ ! test-urlmatch-normalization "+test://acme.co" &&
+ ! test-urlmatch-normalization ".test://acme.co" &&
+ ! test-urlmatch-normalization "schem%6e://" &&
+ test-urlmatch-normalization "x-Test+v1.0://acme.co" &&
+ test "$(test-urlmatch-normalization -p "AbCdeF://x.Y")" = "abcdef://x.y/"
+'
+
+test_expect_success 'url authority' '
+ ! test-urlmatch-normalization "scheme://user:pass@" &&
+ ! test-urlmatch-normalization "scheme://?" &&
+ ! test-urlmatch-normalization "scheme://#" &&
+ ! test-urlmatch-normalization "scheme:///" &&
+ ! test-urlmatch-normalization "scheme://:" &&
+ ! test-urlmatch-normalization "scheme://:555" &&
+ test-urlmatch-normalization "file://user:pass@" &&
+ test-urlmatch-normalization "file://?" &&
+ test-urlmatch-normalization "file://#" &&
+ test-urlmatch-normalization "file:///" &&
+ test-urlmatch-normalization "file://:" &&
+ ! test-urlmatch-normalization "file://:555" &&
+ test-urlmatch-normalization "scheme://user:pass@host" &&
+ test-urlmatch-normalization "scheme://@host" &&
+ test-urlmatch-normalization "scheme://%00@host" &&
+ ! test-urlmatch-normalization "scheme://%%@host" &&
+ ! test-urlmatch-normalization "scheme://host_" &&
+ test-urlmatch-normalization "scheme://user:pass@host/" &&
+ test-urlmatch-normalization "scheme://@host/" &&
+ test-urlmatch-normalization "scheme://host/" &&
+ test-urlmatch-normalization "scheme://host?x" &&
+ test-urlmatch-normalization "scheme://host#x" &&
+ test-urlmatch-normalization "scheme://host/@" &&
+ test-urlmatch-normalization "scheme://host?@x" &&
+ test-urlmatch-normalization "scheme://host#@x" &&
+ test-urlmatch-normalization "scheme://[::1]" &&
+ test-urlmatch-normalization "scheme://[::1]/" &&
+ ! test-urlmatch-normalization "scheme://hos%41/" &&
+ test-urlmatch-normalization "scheme://[invalid....:/" &&
+ test-urlmatch-normalization "scheme://invalid....:]/" &&
+ ! test-urlmatch-normalization "scheme://invalid....:[/" &&
+ ! test-urlmatch-normalization "scheme://invalid....:["
+'
+
+test_expect_success 'url port checks' '
+ test-urlmatch-normalization "xyz://q@some.host:" &&
+ test-urlmatch-normalization "xyz://q@some.host:456/" &&
+ ! test-urlmatch-normalization "xyz://q@some.host:0" &&
+ ! test-urlmatch-normalization "xyz://q@some.host:0000000" &&
+ test-urlmatch-normalization "xyz://q@some.host:0000001?" &&
+ test-urlmatch-normalization "xyz://q@some.host:065535#" &&
+ test-urlmatch-normalization "xyz://q@some.host:65535" &&
+ ! test-urlmatch-normalization "xyz://q@some.host:65536" &&
+ ! test-urlmatch-normalization "xyz://q@some.host:99999" &&
+ ! test-urlmatch-normalization "xyz://q@some.host:100000" &&
+ ! test-urlmatch-normalization "xyz://q@some.host:100001" &&
+ test-urlmatch-normalization "http://q@some.host:80" &&
+ test-urlmatch-normalization "https://q@some.host:443" &&
+ test-urlmatch-normalization "http://q@some.host:80/" &&
+ test-urlmatch-normalization "https://q@some.host:443?" &&
+ ! test-urlmatch-normalization "http://q@:8008" &&
+ ! test-urlmatch-normalization "http://:8080" &&
+ ! test-urlmatch-normalization "http://:" &&
+ test-urlmatch-normalization "xyz://q@some.host:456/" &&
+ test-urlmatch-normalization "xyz://[::1]:456/" &&
+ test-urlmatch-normalization "xyz://[::1]:/" &&
+ ! test-urlmatch-normalization "xyz://[::1]:000/" &&
+ ! test-urlmatch-normalization "xyz://[::1]:0%300/" &&
+ ! test-urlmatch-normalization "xyz://[::1]:0x80/" &&
+ ! test-urlmatch-normalization "xyz://[::1]:4294967297/" &&
+ ! test-urlmatch-normalization "xyz://[::1]:030f/"
+'
+
+test_expect_success 'url port normalization' '
+ test "$(test-urlmatch-normalization -p "http://x:800")" = "http://x:800/" &&
+ test "$(test-urlmatch-normalization -p "http://x:0800")" = "http://x:800/" &&
+ test "$(test-urlmatch-normalization -p "http://x:00000800")" = "http://x:800/" &&
+ test "$(test-urlmatch-normalization -p "http://x:065535")" = "http://x:65535/" &&
+ test "$(test-urlmatch-normalization -p "http://x:1")" = "http://x:1/" &&
+ test "$(test-urlmatch-normalization -p "http://x:80")" = "http://x/" &&
+ test "$(test-urlmatch-normalization -p "http://x:080")" = "http://x/" &&
+ test "$(test-urlmatch-normalization -p "http://x:000000080")" = "http://x/" &&
+ test "$(test-urlmatch-normalization -p "https://x:443")" = "https://x/" &&
+ test "$(test-urlmatch-normalization -p "https://x:0443")" = "https://x/" &&
+ test "$(test-urlmatch-normalization -p "https://x:000000443")" = "https://x/"
+'
+
+test_expect_success 'url general escapes' '
+ ! test-urlmatch-normalization "http://x.y?%fg" &&
+ test "$(test-urlmatch-normalization -p "X://W/%7e%41^%3a")" = "x://w/~A%5E%3A" &&
+ test "$(test-urlmatch-normalization -p "X://W/:/?#[]@")" = "x://w/:/?#[]@" &&
+ test "$(test-urlmatch-normalization -p "X://W/$&()*+,;=")" = "x://w/$&()*+,;=" &&
+ test "$(test-urlmatch-normalization -p "X://W/'\''")" = "x://w/'\''" &&
+ test "$(test-urlmatch-normalization -p "X://W?'\!'")" = "x://w/?'\!'"
+'
+
+test_expect_success 'url high-bit escapes' '
+ test "$(test-urlmatch-normalization -p "$(cat "$tu-1")")" = "x://q/%01%02%03%04%05%06%07%08%0E%0F%10%11%12" &&
+ test "$(test-urlmatch-normalization -p "$(cat "$tu-2")")" = "x://q/%13%14%15%16%17%18%19%1B%1C%1D%1E%1F%7F" &&
+ test "$(test-urlmatch-normalization -p "$(cat "$tu-3")")" = "x://q/%80%81%82%83%84%85%86%87%88%89%8A%8B%8C%8D%8E%8F" &&
+ test "$(test-urlmatch-normalization -p "$(cat "$tu-4")")" = "x://q/%90%91%92%93%94%95%96%97%98%99%9A%9B%9C%9D%9E%9F" &&
+ test "$(test-urlmatch-normalization -p "$(cat "$tu-5")")" = "x://q/%A0%A1%A2%A3%A4%A5%A6%A7%A8%A9%AA%AB%AC%AD%AE%AF" &&
+ test "$(test-urlmatch-normalization -p "$(cat "$tu-6")")" = "x://q/%B0%B1%B2%B3%B4%B5%B6%B7%B8%B9%BA%BB%BC%BD%BE%BF" &&
+ test "$(test-urlmatch-normalization -p "$(cat "$tu-7")")" = "x://q/%C0%C1%C2%C3%C4%C5%C6%C7%C8%C9%CA%CB%CC%CD%CE%CF" &&
+ test "$(test-urlmatch-normalization -p "$(cat "$tu-8")")" = "x://q/%D0%D1%D2%D3%D4%D5%D6%D7%D8%D9%DA%DB%DC%DD%DE%DF" &&
+ test "$(test-urlmatch-normalization -p "$(cat "$tu-9")")" = "x://q/%E0%E1%E2%E3%E4%E5%E6%E7%E8%E9%EA%EB%EC%ED%EE%EF" &&
+ test "$(test-urlmatch-normalization -p "$(cat "$tu-10")")" = "x://q/%F0%F1%F2%F3%F4%F5%F6%F7%F8%F9%FA%FB%FC%FD%FE%FF" &&
+ test "$(test-urlmatch-normalization -p "$(cat "$tu-11")")" = "x://q/%C2%80%DF%BF%E0%A0%80%EF%BF%BD%F0%90%80%80%F0%AF%BF%BD"
+'
+
+test_expect_success 'url username/password escapes' '
+ test "$(test-urlmatch-normalization -p "x://%41%62(^):%70+d@foo")" = "x://Ab(%5E):p+d@foo/"
+'
+
+test_expect_success 'url normalized lengths' '
+ test "$(test-urlmatch-normalization -l "Http://%4d%65:%4d^%70@The.Host")" = 25 &&
+ test "$(test-urlmatch-normalization -l "http://%41:%42@x.y/%61/")" = 17 &&
+ test "$(test-urlmatch-normalization -l "http://@x.y/^")" = 15
+'
+
+test_expect_success 'url . and .. segments' '
+ test "$(test-urlmatch-normalization -p "x://y/.")" = "x://y/" &&
+ test "$(test-urlmatch-normalization -p "x://y/./")" = "x://y/" &&
+ test "$(test-urlmatch-normalization -p "x://y/a/.")" = "x://y/a" &&
+ test "$(test-urlmatch-normalization -p "x://y/a/./")" = "x://y/a/" &&
+ test "$(test-urlmatch-normalization -p "x://y/.?")" = "x://y/?" &&
+ test "$(test-urlmatch-normalization -p "x://y/./?")" = "x://y/?" &&
+ test "$(test-urlmatch-normalization -p "x://y/a/.?")" = "x://y/a?" &&
+ test "$(test-urlmatch-normalization -p "x://y/a/./?")" = "x://y/a/?" &&
+ test "$(test-urlmatch-normalization -p "x://y/a/./b/.././../c")" = "x://y/c" &&
+ test "$(test-urlmatch-normalization -p "x://y/a/./b/../.././c/")" = "x://y/c/" &&
+ test "$(test-urlmatch-normalization -p "x://y/a/./b/.././../c/././.././.")" = "x://y/" &&
+ ! test-urlmatch-normalization "x://y/a/./b/.././../c/././.././.." &&
+ test "$(test-urlmatch-normalization -p "x://y/a/./?/././..")" = "x://y/a/?/././.." &&
+ test "$(test-urlmatch-normalization -p "x://y/%2e/")" = "x://y/" &&
+ test "$(test-urlmatch-normalization -p "x://y/%2E/")" = "x://y/" &&
+ test "$(test-urlmatch-normalization -p "x://y/a/%2e./")" = "x://y/" &&
+ test "$(test-urlmatch-normalization -p "x://y/b/.%2E/")" = "x://y/" &&
+ test "$(test-urlmatch-normalization -p "x://y/c/%2e%2E/")" = "x://y/"
+'
+
+# http://@foo specifies an empty user name but does not specify a password
+# http://foo specifies neither a user name nor a password
+# So they should not be equivalent
+test_expect_success 'url equivalents' '
+ test-urlmatch-normalization "httP://x" "Http://X/" &&
+ test-urlmatch-normalization "Http://%4d%65:%4d^%70@The.Host" "hTTP://Me:%4D^p@the.HOST:80/" &&
+ ! test-urlmatch-normalization "https://@x.y/^" "httpS://x.y:443/^" &&
+ test-urlmatch-normalization "https://@x.y/^" "httpS://@x.y:0443/^" &&
+ test-urlmatch-normalization "https://@x.y/^/../abc" "httpS://@x.y:0443/abc" &&
+ test-urlmatch-normalization "https://@x.y/^/.." "httpS://@x.y:0443/"
+'
+
+test_done
--- /dev/null
+The url data files in this directory contain URLs with characters
+in the range 0x01-0x1f and 0x7f-0xff to test the proper normalization
+of unprintable characters.
+
+A select few characters in the 0x01-0x1f range are skipped to help
+avoid problems running the test itself.
+
+The urls are in test files in this directory rather than being
+embedded in the test script for portability.
echo $sha1 | git cat-file --batch-check="%(objecttype) %(objectname)" >actual &&
test_cmp expect actual
'
+
+ test_expect_success '--batch-check with %(rest)' '
+ echo "$type this is some extra content" >expect &&
+ echo "$sha1 this is some extra content" |
+ git cat-file --batch-check="%(objecttype) %(rest)" >actual &&
+ test_cmp expect actual
+ '
}
hello_content="Hello World"
run_tests 'blob' $hello_sha1 $hello_size "$hello_content" "$hello_content"
+test_expect_success '--batch-check without %(rest) considers whole line' '
+ echo "$hello_sha1 blob $hello_size" >expect &&
+ git update-index --add --cacheinfo 100644 $hello_sha1 "white space" &&
+ test_when_finished "git update-index --remove \"white space\"" &&
+ echo ":white space" | git cat-file --batch-check >actual &&
+ test_cmp expect actual
+'
+
tree_sha1=$(git write-tree)
tree_size=33
tree_pretty_content="100644 blob $hello_sha1 hello"
test_cmp expect actual
'
+test_expect_success '--int is at least 64 bits' '
+ git config giga.watts 121g &&
+ echo 129922760704 >expect &&
+ git config --int --get giga.watts >actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'invalid unit' '
git config aninvalid.unit "1auto" &&
echo 1auto >expect &&
git config aninvalid.unit >actual &&
test_cmp expect actual &&
- cat > expect <<-\EOF
- fatal: bad config value for '\''aninvalid.unit'\'' in .git/config
+ cat >expect <<-\EOF
+ fatal: bad numeric config value '\''1auto'\'' for '\''aninvalid.unit'\'' in .git/config: invalid unit
EOF
test_must_fail git config --int --get aninvalid.unit 2>actual &&
- test_cmp actual expect
+ test_i18ncmp expect actual
'
cat > expect << EOF
grep " line 3 " error
'
+test_expect_success 'urlmatch' '
+ cat >.git/config <<-\EOF &&
+ [http]
+ sslVerify
+ [http "https://weak.example.com"]
+ sslVerify = false
+ cookieFile = /tmp/cookie.txt
+ EOF
+
+ echo true >expect &&
+ git config --bool --get-urlmatch http.SSLverify https://good.example.com >actual &&
+ test_cmp expect actual &&
+
+ echo false >expect &&
+ git config --bool --get-urlmatch http.sslverify https://weak.example.com >actual &&
+ test_cmp expect actual &&
+
+ {
+ echo http.cookiefile /tmp/cookie.txt &&
+ echo http.sslverify false
+ } >expect &&
+ git config --get-urlmatch HTTP https://weak.example.com >actual &&
+ test_cmp expect actual
+'
+
# good section hygiene
test_expect_failure 'unsetting the last key in a section removes header' '
cat >.git/config <<-\EOF &&
'git cat-file blob master@{2005-05-26 23:42}:F (expect OTHER)' \
'test OTHER = $(git cat-file blob "master@{2005-05-26 23:42}:F")'
+a=refs/heads/a
+b=refs/heads/b
+c=refs/heads/c
+E='""'
+F='%s\0'
+pws='path with space'
+
+test_expect_success 'stdin test setup' '
+ echo "$pws" >"$pws" &&
+ git add -- "$pws" &&
+ git commit -m "$pws"
+'
+
+test_expect_success '-z fails without --stdin' '
+ test_must_fail git update-ref -z $m $m $m 2>err &&
+ grep "usage: git update-ref" err
+'
+
+test_expect_success 'stdin works with no input' '
+ >stdin &&
+ git update-ref --stdin <stdin &&
+ git rev-parse --verify -q $m
+'
+
+test_expect_success 'stdin fails on empty line' '
+ echo "" >stdin &&
+ test_must_fail git update-ref --stdin <stdin 2>err &&
+ grep "fatal: empty command in input" err
+'
+
+test_expect_success 'stdin fails on only whitespace' '
+ echo " " >stdin &&
+ test_must_fail git update-ref --stdin <stdin 2>err &&
+ grep "fatal: whitespace before command: " err
+'
+
+test_expect_success 'stdin fails on leading whitespace' '
+ echo " create $a $m" >stdin &&
+ test_must_fail git update-ref --stdin <stdin 2>err &&
+ grep "fatal: whitespace before command: create $a $m" err
+'
+
+test_expect_success 'stdin fails on unknown command' '
+ echo "unknown $a" >stdin &&
+ test_must_fail git update-ref --stdin <stdin 2>err &&
+ grep "fatal: unknown command: unknown $a" err
+'
+
+test_expect_success 'stdin fails on badly quoted input' '
+ echo "create $a \"master" >stdin &&
+ test_must_fail git update-ref --stdin <stdin 2>err &&
+ grep "fatal: badly quoted argument: \\\"master" err
+'
+
+test_expect_success 'stdin fails on arguments not separated by space' '
+ echo "create \"$a\"master" >stdin &&
+ test_must_fail git update-ref --stdin <stdin 2>err &&
+ grep "fatal: expected SP but got: master" err
+'
+
+test_expect_success 'stdin fails create with no ref' '
+ echo "create " >stdin &&
+ test_must_fail git update-ref --stdin <stdin 2>err &&
+ grep "fatal: create line missing <ref>" err
+'
+
+test_expect_success 'stdin fails create with bad ref name' '
+ echo "create ~a $m" >stdin &&
+ test_must_fail git update-ref --stdin <stdin 2>err &&
+ grep "fatal: invalid ref format: ~a" err
+'
+
+test_expect_success 'stdin fails create with no new value' '
+ echo "create $a" >stdin &&
+ test_must_fail git update-ref --stdin <stdin 2>err &&
+ grep "fatal: create $a missing <newvalue>" err
+'
+
+test_expect_success 'stdin fails create with too many arguments' '
+ echo "create $a $m $m" >stdin &&
+ test_must_fail git update-ref --stdin <stdin 2>err &&
+ grep "fatal: create $a has extra input: $m" err
+'
+
+test_expect_success 'stdin fails update with no ref' '
+ echo "update " >stdin &&
+ test_must_fail git update-ref --stdin <stdin 2>err &&
+ grep "fatal: update line missing <ref>" err
+'
+
+test_expect_success 'stdin fails update with bad ref name' '
+ echo "update ~a $m" >stdin &&
+ test_must_fail git update-ref --stdin <stdin 2>err &&
+ grep "fatal: invalid ref format: ~a" err
+'
+
+test_expect_success 'stdin fails update with no new value' '
+ echo "update $a" >stdin &&
+ test_must_fail git update-ref --stdin <stdin 2>err &&
+ grep "fatal: update $a missing <newvalue>" err
+'
+
+test_expect_success 'stdin fails update with too many arguments' '
+ echo "update $a $m $m $m" >stdin &&
+ test_must_fail git update-ref --stdin <stdin 2>err &&
+ grep "fatal: update $a has extra input: $m" err
+'
+
+test_expect_success 'stdin fails delete with no ref' '
+ echo "delete " >stdin &&
+ test_must_fail git update-ref --stdin <stdin 2>err &&
+ grep "fatal: delete line missing <ref>" err
+'
+
+test_expect_success 'stdin fails delete with bad ref name' '
+ echo "delete ~a $m" >stdin &&
+ test_must_fail git update-ref --stdin <stdin 2>err &&
+ grep "fatal: invalid ref format: ~a" err
+'
+
+test_expect_success 'stdin fails delete with too many arguments' '
+ echo "delete $a $m $m" >stdin &&
+ test_must_fail git update-ref --stdin <stdin 2>err &&
+ grep "fatal: delete $a has extra input: $m" err
+'
+
+test_expect_success 'stdin fails verify with too many arguments' '
+ echo "verify $a $m $m" >stdin &&
+ test_must_fail git update-ref --stdin <stdin 2>err &&
+ grep "fatal: verify $a has extra input: $m" err
+'
+
+test_expect_success 'stdin fails option with unknown name' '
+ echo "option unknown" >stdin &&
+ test_must_fail git update-ref --stdin <stdin 2>err &&
+ grep "fatal: option unknown: unknown" err
+'
+
+test_expect_success 'stdin fails with duplicate refs' '
+ cat >stdin <<-EOF &&
+ create $a $m
+ create $b $m
+ create $a $m
+ EOF
+ test_must_fail git update-ref --stdin <stdin 2>err &&
+ grep "fatal: Multiple updates for ref '"'"'$a'"'"' not allowed." err
+'
+
+test_expect_success 'stdin create ref works' '
+ echo "create $a $m" >stdin &&
+ git update-ref --stdin <stdin &&
+ git rev-parse $m >expect &&
+ git rev-parse $a >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'stdin update ref creates with zero old value' '
+ echo "update $b $m $Z" >stdin &&
+ git update-ref --stdin <stdin &&
+ git rev-parse $m >expect &&
+ git rev-parse $b >actual &&
+ test_cmp expect actual &&
+ git update-ref -d $b
+'
+
+test_expect_success 'stdin update ref creates with empty old value' '
+ echo "update $b $m $E" >stdin &&
+ git update-ref --stdin <stdin &&
+ git rev-parse $m >expect &&
+ git rev-parse $b >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'stdin create ref works with path with space to blob' '
+ echo "create refs/blobs/pws \"$m:$pws\"" >stdin &&
+ git update-ref --stdin <stdin &&
+ git rev-parse "$m:$pws" >expect &&
+ git rev-parse refs/blobs/pws >actual &&
+ test_cmp expect actual &&
+ git update-ref -d refs/blobs/pws
+'
+
+test_expect_success 'stdin update ref fails with wrong old value' '
+ echo "update $c $m $m~1" >stdin &&
+ test_must_fail git update-ref --stdin <stdin 2>err &&
+ grep "fatal: Cannot lock the ref '"'"'$c'"'"'" err &&
+ test_must_fail git rev-parse --verify -q $c
+'
+
+test_expect_success 'stdin update ref fails with bad old value' '
+ echo "update $c $m does-not-exist" >stdin &&
+ test_must_fail git update-ref --stdin <stdin 2>err &&
+ grep "fatal: invalid old value for ref $c: does-not-exist" err &&
+ test_must_fail git rev-parse --verify -q $c
+'
+
+test_expect_success 'stdin create ref fails with bad new value' '
+ echo "create $c does-not-exist" >stdin &&
+ test_must_fail git update-ref --stdin <stdin 2>err &&
+ grep "fatal: invalid new value for ref $c: does-not-exist" err &&
+ test_must_fail git rev-parse --verify -q $c
+'
+
+test_expect_success 'stdin create ref fails with zero new value' '
+ echo "create $c " >stdin &&
+ test_must_fail git update-ref --stdin <stdin 2>err &&
+ grep "fatal: create $c given zero new value" err &&
+ test_must_fail git rev-parse --verify -q $c
+'
+
+test_expect_success 'stdin update ref works with right old value' '
+ echo "update $b $m~1 $m" >stdin &&
+ git update-ref --stdin <stdin &&
+ git rev-parse $m~1 >expect &&
+ git rev-parse $b >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'stdin delete ref fails with wrong old value' '
+ echo "delete $a $m~1" >stdin &&
+ test_must_fail git update-ref --stdin <stdin 2>err &&
+ grep "fatal: Cannot lock the ref '"'"'$a'"'"'" err &&
+ git rev-parse $m >expect &&
+ git rev-parse $a >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'stdin delete ref fails with zero old value' '
+ echo "delete $a " >stdin &&
+ test_must_fail git update-ref --stdin <stdin 2>err &&
+ grep "fatal: delete $a given zero old value" err &&
+ git rev-parse $m >expect &&
+ git rev-parse $a >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'stdin update symref works option no-deref' '
+ git symbolic-ref TESTSYMREF $b &&
+ cat >stdin <<-EOF &&
+ option no-deref
+ update TESTSYMREF $a $b
+ EOF
+ git update-ref --stdin <stdin &&
+ git rev-parse TESTSYMREF >expect &&
+ git rev-parse $a >actual &&
+ test_cmp expect actual &&
+ git rev-parse $m~1 >expect &&
+ git rev-parse $b >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'stdin delete symref works option no-deref' '
+ git symbolic-ref TESTSYMREF $b &&
+ cat >stdin <<-EOF &&
+ option no-deref
+ delete TESTSYMREF $b
+ EOF
+ git update-ref --stdin <stdin &&
+ test_must_fail git rev-parse --verify -q TESTSYMREF &&
+ git rev-parse $m~1 >expect &&
+ git rev-parse $b >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'stdin delete ref works with right old value' '
+ echo "delete $b $m~1" >stdin &&
+ git update-ref --stdin <stdin &&
+ test_must_fail git rev-parse --verify -q $b
+'
+
+test_expect_success 'stdin update/create/verify combination works' '
+ cat >stdin <<-EOF &&
+ update $a $m
+ create $b $m
+ verify $c
+ EOF
+ git update-ref --stdin <stdin &&
+ git rev-parse $m >expect &&
+ git rev-parse $a >actual &&
+ test_cmp expect actual &&
+ git rev-parse $b >actual &&
+ test_cmp expect actual &&
+ test_must_fail git rev-parse --verify -q $c
+'
+
+test_expect_success 'stdin update refs works with identity updates' '
+ cat >stdin <<-EOF &&
+ update $a $m $m
+ update $b $m $m
+ update $c $Z $E
+ EOF
+ git update-ref --stdin <stdin &&
+ git rev-parse $m >expect &&
+ git rev-parse $a >actual &&
+ test_cmp expect actual &&
+ git rev-parse $b >actual &&
+ test_cmp expect actual &&
+ test_must_fail git rev-parse --verify -q $c
+'
+
+test_expect_success 'stdin update refs fails with wrong old value' '
+ git update-ref $c $m &&
+ cat >stdin <<-EOF &&
+ update $a $m $m
+ update $b $m $m
+ update $c ''
+ EOF
+ test_must_fail git update-ref --stdin <stdin 2>err &&
+ grep "fatal: Cannot lock the ref '"'"'$c'"'"'" err &&
+ git rev-parse $m >expect &&
+ git rev-parse $a >actual &&
+ test_cmp expect actual &&
+ git rev-parse $b >actual &&
+ test_cmp expect actual &&
+ git rev-parse $c >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'stdin delete refs works with packed and loose refs' '
+ git pack-refs --all &&
+ git update-ref $c $m~1 &&
+ cat >stdin <<-EOF &&
+ delete $a $m
+ update $b $Z $m
+ update $c $E $m~1
+ EOF
+ git update-ref --stdin <stdin &&
+ test_must_fail git rev-parse --verify -q $a &&
+ test_must_fail git rev-parse --verify -q $b &&
+ test_must_fail git rev-parse --verify -q $c
+'
+
+test_expect_success 'stdin -z works on empty input' '
+ >stdin &&
+ git update-ref -z --stdin <stdin &&
+ git rev-parse --verify -q $m
+'
+
+test_expect_success 'stdin -z fails on empty line' '
+ echo "" >stdin &&
+ test_must_fail git update-ref -z --stdin <stdin 2>err &&
+ grep "fatal: whitespace before command: " err
+'
+
+test_expect_success 'stdin -z fails on empty command' '
+ printf $F "" >stdin &&
+ test_must_fail git update-ref -z --stdin <stdin 2>err &&
+ grep "fatal: empty command in input" err
+'
+
+test_expect_success 'stdin -z fails on only whitespace' '
+ printf $F " " >stdin &&
+ test_must_fail git update-ref -z --stdin <stdin 2>err &&
+ grep "fatal: whitespace before command: " err
+'
+
+test_expect_success 'stdin -z fails on leading whitespace' '
+ printf $F " create $a" "$m" >stdin &&
+ test_must_fail git update-ref -z --stdin <stdin 2>err &&
+ grep "fatal: whitespace before command: create $a" err
+'
+
+test_expect_success 'stdin -z fails on unknown command' '
+ printf $F "unknown $a" >stdin &&
+ test_must_fail git update-ref -z --stdin <stdin 2>err &&
+ grep "fatal: unknown command: unknown $a" err
+'
+
+test_expect_success 'stdin -z fails create with no ref' '
+ printf $F "create " >stdin &&
+ test_must_fail git update-ref -z --stdin <stdin 2>err &&
+ grep "fatal: create line missing <ref>" err
+'
+
+test_expect_success 'stdin -z fails create with bad ref name' '
+ printf $F "create ~a " "$m" >stdin &&
+ test_must_fail git update-ref -z --stdin <stdin 2>err &&
+ grep "fatal: invalid ref format: ~a " err
+'
+
+test_expect_success 'stdin -z fails create with no new value' '
+ printf $F "create $a" >stdin &&
+ test_must_fail git update-ref -z --stdin <stdin 2>err &&
+ grep "fatal: create $a missing <newvalue>" err
+'
+
+test_expect_success 'stdin -z fails create with too many arguments' '
+ printf $F "create $a" "$m" "$m" >stdin &&
+ test_must_fail git update-ref -z --stdin <stdin 2>err &&
+ grep "fatal: unknown command: $m" err
+'
+
+test_expect_success 'stdin -z fails update with no ref' '
+ printf $F "update " >stdin &&
+ test_must_fail git update-ref -z --stdin <stdin 2>err &&
+ grep "fatal: update line missing <ref>" err
+'
+
+test_expect_success 'stdin -z fails update with bad ref name' '
+ printf $F "update ~a" "$m" >stdin &&
+ test_must_fail git update-ref -z --stdin <stdin 2>err &&
+ grep "fatal: invalid ref format: ~a" err
+'
+
+test_expect_success 'stdin -z fails update with no new value' '
+ printf $F "update $a" >stdin &&
+ test_must_fail git update-ref -z --stdin <stdin 2>err &&
+ grep "fatal: update $a missing <newvalue>" err
+'
+
+test_expect_success 'stdin -z fails update with no old value' '
+ printf $F "update $a" "$m" >stdin &&
+ test_must_fail git update-ref -z --stdin <stdin 2>err &&
+ grep "fatal: update $a missing \\[<oldvalue>\\] NUL" err
+'
+
+test_expect_success 'stdin -z fails update with too many arguments' '
+ printf $F "update $a" "$m" "$m" "$m" >stdin &&
+ test_must_fail git update-ref -z --stdin <stdin 2>err &&
+ grep "fatal: unknown command: $m" err
+'
+
+test_expect_success 'stdin -z fails delete with no ref' '
+ printf $F "delete " >stdin &&
+ test_must_fail git update-ref -z --stdin <stdin 2>err &&
+ grep "fatal: delete line missing <ref>" err
+'
+
+test_expect_success 'stdin -z fails delete with bad ref name' '
+ printf $F "delete ~a" "$m" >stdin &&
+ test_must_fail git update-ref -z --stdin <stdin 2>err &&
+ grep "fatal: invalid ref format: ~a" err
+'
+
+test_expect_success 'stdin -z fails delete with no old value' '
+ printf $F "delete $a" >stdin &&
+ test_must_fail git update-ref -z --stdin <stdin 2>err &&
+ grep "fatal: delete $a missing \\[<oldvalue>\\] NUL" err
+'
+
+test_expect_success 'stdin -z fails delete with too many arguments' '
+ printf $F "delete $a" "$m" "$m" >stdin &&
+ test_must_fail git update-ref -z --stdin <stdin 2>err &&
+ grep "fatal: unknown command: $m" err
+'
+
+test_expect_success 'stdin -z fails verify with too many arguments' '
+ printf $F "verify $a" "$m" "$m" >stdin &&
+ test_must_fail git update-ref -z --stdin <stdin 2>err &&
+ grep "fatal: unknown command: $m" err
+'
+
+test_expect_success 'stdin -z fails verify with no old value' '
+ printf $F "verify $a" >stdin &&
+ test_must_fail git update-ref -z --stdin <stdin 2>err &&
+ grep "fatal: verify $a missing \\[<oldvalue>\\] NUL" err
+'
+
+test_expect_success 'stdin -z fails option with unknown name' '
+ printf $F "option unknown" >stdin &&
+ test_must_fail git update-ref -z --stdin <stdin 2>err &&
+ grep "fatal: option unknown: unknown" err
+'
+
+test_expect_success 'stdin -z fails with duplicate refs' '
+ printf $F "create $a" "$m" "create $b" "$m" "create $a" "$m" >stdin &&
+ test_must_fail git update-ref -z --stdin <stdin 2>err &&
+ grep "fatal: Multiple updates for ref '"'"'$a'"'"' not allowed." err
+'
+
+test_expect_success 'stdin -z create ref works' '
+ printf $F "create $a" "$m" >stdin &&
+ git update-ref -z --stdin <stdin &&
+ git rev-parse $m >expect &&
+ git rev-parse $a >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'stdin -z update ref creates with zero old value' '
+ printf $F "update $b" "$m" "$Z" >stdin &&
+ git update-ref -z --stdin <stdin &&
+ git rev-parse $m >expect &&
+ git rev-parse $b >actual &&
+ test_cmp expect actual &&
+ git update-ref -d $b
+'
+
+test_expect_success 'stdin -z update ref creates with empty old value' '
+ printf $F "update $b" "$m" "" >stdin &&
+ git update-ref -z --stdin <stdin &&
+ git rev-parse $m >expect &&
+ git rev-parse $b >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'stdin -z create ref works with path with space to blob' '
+ printf $F "create refs/blobs/pws" "$m:$pws" >stdin &&
+ git update-ref -z --stdin <stdin &&
+ git rev-parse "$m:$pws" >expect &&
+ git rev-parse refs/blobs/pws >actual &&
+ test_cmp expect actual &&
+ git update-ref -d refs/blobs/pws
+'
+
+test_expect_success 'stdin -z update ref fails with wrong old value' '
+ printf $F "update $c" "$m" "$m~1" >stdin &&
+ test_must_fail git update-ref -z --stdin <stdin 2>err &&
+ grep "fatal: Cannot lock the ref '"'"'$c'"'"'" err &&
+ test_must_fail git rev-parse --verify -q $c
+'
+
+test_expect_success 'stdin -z update ref fails with bad old value' '
+ printf $F "update $c" "$m" "does-not-exist" >stdin &&
+ test_must_fail git update-ref -z --stdin <stdin 2>err &&
+ grep "fatal: invalid old value for ref $c: does-not-exist" err &&
+ test_must_fail git rev-parse --verify -q $c
+'
+
+test_expect_success 'stdin -z create ref fails with bad new value' '
+ printf $F "create $c" "does-not-exist" >stdin &&
+ test_must_fail git update-ref -z --stdin <stdin 2>err &&
+ grep "fatal: invalid new value for ref $c: does-not-exist" err &&
+ test_must_fail git rev-parse --verify -q $c
+'
+
+test_expect_success 'stdin -z create ref fails with zero new value' '
+ printf $F "create $c" "" >stdin &&
+ test_must_fail git update-ref -z --stdin <stdin 2>err &&
+ grep "fatal: create $c given zero new value" err &&
+ test_must_fail git rev-parse --verify -q $c
+'
+
+test_expect_success 'stdin -z update ref works with right old value' '
+ printf $F "update $b" "$m~1" "$m" >stdin &&
+ git update-ref -z --stdin <stdin &&
+ git rev-parse $m~1 >expect &&
+ git rev-parse $b >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'stdin -z delete ref fails with wrong old value' '
+ printf $F "delete $a" "$m~1" >stdin &&
+ test_must_fail git update-ref -z --stdin <stdin 2>err &&
+ grep "fatal: Cannot lock the ref '"'"'$a'"'"'" err &&
+ git rev-parse $m >expect &&
+ git rev-parse $a >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'stdin -z delete ref fails with zero old value' '
+ printf $F "delete $a" "$Z" >stdin &&
+ test_must_fail git update-ref -z --stdin <stdin 2>err &&
+ grep "fatal: delete $a given zero old value" err &&
+ git rev-parse $m >expect &&
+ git rev-parse $a >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'stdin -z update symref works option no-deref' '
+ git symbolic-ref TESTSYMREF $b &&
+ printf $F "option no-deref" "update TESTSYMREF" "$a" "$b" >stdin &&
+ git update-ref -z --stdin <stdin &&
+ git rev-parse TESTSYMREF >expect &&
+ git rev-parse $a >actual &&
+ test_cmp expect actual &&
+ git rev-parse $m~1 >expect &&
+ git rev-parse $b >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'stdin -z delete symref works option no-deref' '
+ git symbolic-ref TESTSYMREF $b &&
+ printf $F "option no-deref" "delete TESTSYMREF" "$b" >stdin &&
+ git update-ref -z --stdin <stdin &&
+ test_must_fail git rev-parse --verify -q TESTSYMREF &&
+ git rev-parse $m~1 >expect &&
+ git rev-parse $b >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'stdin -z delete ref works with right old value' '
+ printf $F "delete $b" "$m~1" >stdin &&
+ git update-ref -z --stdin <stdin &&
+ test_must_fail git rev-parse --verify -q $b
+'
+
+test_expect_success 'stdin -z update/create/verify combination works' '
+ printf $F "update $a" "$m" "" "create $b" "$m" "verify $c" "" >stdin &&
+ git update-ref -z --stdin <stdin &&
+ git rev-parse $m >expect &&
+ git rev-parse $a >actual &&
+ test_cmp expect actual &&
+ git rev-parse $b >actual &&
+ test_cmp expect actual &&
+ test_must_fail git rev-parse --verify -q $c
+'
+
+test_expect_success 'stdin -z update refs works with identity updates' '
+ printf $F "update $a" "$m" "$m" "update $b" "$m" "$m" "update $c" "$Z" "" >stdin &&
+ git update-ref -z --stdin <stdin &&
+ git rev-parse $m >expect &&
+ git rev-parse $a >actual &&
+ test_cmp expect actual &&
+ git rev-parse $b >actual &&
+ test_cmp expect actual &&
+ test_must_fail git rev-parse --verify -q $c
+'
+
+test_expect_success 'stdin -z update refs fails with wrong old value' '
+ git update-ref $c $m &&
+ printf $F "update $a" "$m" "$m" "update $b" "$m" "$m" "update $c" "" "$Z" >stdin &&
+ test_must_fail git update-ref -z --stdin <stdin 2>err &&
+ grep "fatal: Cannot lock the ref '"'"'$c'"'"'" err &&
+ git rev-parse $m >expect &&
+ git rev-parse $a >actual &&
+ test_cmp expect actual &&
+ git rev-parse $b >actual &&
+ test_cmp expect actual &&
+ git rev-parse $c >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'stdin -z delete refs works with packed and loose refs' '
+ git pack-refs --all &&
+ git update-ref $c $m~1 &&
+ printf $F "delete $a" "$m" "update $b" "$Z" "$m" "update $c" "" "$m~1" >stdin &&
+ git update-ref -z --stdin <stdin &&
+ test_must_fail git rev-parse --verify -q $a &&
+ test_must_fail git rev-parse --verify -q $b &&
+ test_must_fail git rev-parse --verify -q $c
+'
+
test_done
test_cmp expect actual
'
+# This guards against the alternative of showing the diffs vs. the
+# reflog ancestor. The reflog used is designed to list the commits
+# more than once, so as to exercise the corresponding logic.
+test_expect_success 'git log -g -p shows diffs vs. parents' '
+ test_commit two &&
+ git branch flipflop &&
+ git update-ref refs/heads/flipflop -m flip1 HEAD^ &&
+ git update-ref refs/heads/flipflop -m flop1 HEAD &&
+ git update-ref refs/heads/flipflop -m flip2 HEAD^ &&
+ git log -g -p flipflop >reflog &&
+ grep -v ^Reflog reflog >actual &&
+ git log -1 -p HEAD^ >log.one &&
+ git log -1 -p HEAD >log.two &&
+ (
+ cat log.one; echo
+ cat log.two; echo
+ cat log.one; echo
+ cat log.two
+ ) >expect &&
+ test_cmp expect actual
+'
+
test_done
test_must_fail git rev-parse blob-tag^{tree}
'
+test_expect_success 'ref^{tag}' '
+ test_must_fail git rev-parse HEAD^{tag} &&
+ git rev-parse commit-tag >expected &&
+ git rev-parse commit-tag^{tag} >actual &&
+ test_cmp expected actual
+'
+
test_expect_success 'ref^{/.}' '
git rev-parse master >expected &&
git rev-parse master^{/.} >actual &&
cd repo_c &&
test_commit c_master &&
git checkout -b bar &&
- test_commit c_bar
+ test_commit c_bar &&
git checkout -b spam &&
test_commit c_spam
) &&
cd repo_d &&
test_commit d_master &&
git checkout -b baz &&
- test_commit f_baz
+ test_commit d_baz &&
git checkout -b eggs &&
- test_commit c_eggs
+ test_commit d_eggs
) &&
git remote add repo_c repo_c &&
git config remote.repo_c.fetch \
git config core.excludesFile excludes-file
-git status | grep "^# " > output
+git -c status.displayCommentPrefix=true status | grep "^# " > output
cat > expect << EOF
# .gitignore
path1 - a symlink
path2/file2 - a file in a directory
path3/file3 - a file in a directory
+ pathx/ju - a file in a directory
submod1/ - a submodule
submod2/ - another submodule
path4 - a file
path5 - a symlink
path6/file6 - a file in a directory
+ pathx/ju/nk - a file in a directory to be killed
submod1/ - a submodule (modified from the cache)
submod2/ - a submodule (matches the cache)
test_expect_success 'git update-index --add to add various paths.' '
date >path0 &&
test_ln_s_add xyzzy path1 &&
- mkdir path2 path3 &&
+ mkdir path2 path3 pathx &&
date >path2/file2 &&
date >path3/file3 &&
+ >pathx/ju &&
: >path7 &&
date >path8 &&
: >path9 &&
date >path10 &&
- git update-index --add -- path0 path?/file? path7 path8 path9 path10 &&
+ git update-index --add -- path0 path?/file? pathx/ju path7 path8 path9 path10 &&
for i in 1 2
do
git init submod$i &&
date >path3 &&
date >path5
fi &&
- mkdir path0 path1 path6 &&
+ mkdir -p path0 path1 path6 pathx/ju &&
date >path0/file0 &&
date >path1/file1 &&
date >path6/file6 &&
: >path8 &&
: >path9 &&
touch path10 &&
- git ls-files -k >.output
-'
-
-test_expect_success 'validate git ls-files -k output.' '
- cat >.expected <<-\EOF &&
+ >pathx/ju/nk &&
+ cat >.expected <<-\EOF
path0/file0
path1/file1
path2
path3
+ pathx/ju/nk
EOF
+'
+
+test_expect_success 'git ls-files -k output (w/o icase)' '
+ git ls-files -k >.output
+ test_cmp .expected .output
+'
+
+test_expect_success 'git ls-files -k output (w/ icase)' '
+ git -c core.ignorecase=true ls-files -k >.output
test_cmp .expected .output
'
path3/file3
path7
path8
+ pathx/ju
submod1
EOF
test_cmp .expected .output
echo World >>A &&
git update-index --add A &&
git commit -m "Second commit." &&
- HEAD=$(git rev-parse --verify HEAD)'
+ HEAD=$(git rev-parse --verify HEAD)
+'
test_expect_success 'git branch --help should not have created a bogus branch' '
test_might_fail git branch --help </dev/null >/dev/null 2>/dev/null &&
test_expect_success 'tracking setup fails on non-matching refspec' '
git config remote.local.url . &&
- git config remote.local.fetch refs/heads/s:refs/remotes/local/s &&
+ git config remote.local.fetch refs/heads/*:refs/remotes/local/* &&
(git show-ref -q refs/remotes/local/master || git fetch local) &&
+ git config remote.local.fetch refs/heads/s:refs/remotes/local/s &&
test_must_fail git branch --track my5 local/master &&
test_must_fail git config branch.my5.remote &&
test_must_fail git config branch.my5.merge
test_expect_success 'no tracking without .fetch entries' '
git config branch.autosetupmerge true &&
git branch my6 s &&
- git config branch.automsetupmerge false &&
+ git config branch.autosetupmerge false &&
test -z "$(git config branch.my6.remote)" &&
test -z "$(git config branch.my6.merge)"
'
test_expect_success 'use --set-upstream-to modify HEAD' '
test_config branch.master.remote foo &&
test_config branch.master.merge foo &&
- git branch my12
+ git branch my12 &&
git branch --set-upstream-to my12 &&
test "$(git config branch.master.remote)" = "." &&
test "$(git config branch.master.merge)" = "refs/heads/my12"
'
test_expect_success 'use --set-upstream-to modify a particular branch' '
- git branch my13
+ git branch my13 &&
git branch --set-upstream-to master my13 &&
test "$(git config branch.my13.remote)" = "." &&
test "$(git config branch.my13.merge)" = "refs/heads/master"
'
test_expect_success 'test --unset-upstream on HEAD' '
- git branch my14
+ git branch my14 &&
test_config branch.master.remote foo &&
test_config branch.master.merge foo &&
git branch --set-upstream-to my14 &&
'
test_expect_success 'test --unset-upstream on a particular branch' '
- git branch my15
+ git branch my15 &&
git branch --set-upstream-to master my14 &&
git branch --unset-upstream my14 &&
test_must_fail git config branch.my14.remote &&
test_must_fail git branch --merged 0000000000000000000000000000000000000000
'
+test_expect_success 'tracking with unexpected .fetch refspec' '
+ rm -rf a b c d &&
+ git init a &&
+ (
+ cd a &&
+ test_commit a
+ ) &&
+ git init b &&
+ (
+ cd b &&
+ test_commit b
+ ) &&
+ git init c &&
+ (
+ cd c &&
+ test_commit c &&
+ git remote add a ../a &&
+ git remote add b ../b &&
+ git fetch --all
+ ) &&
+ git init d &&
+ (
+ cd d &&
+ git remote add c ../c &&
+ git config remote.c.fetch "+refs/remotes/*:refs/remotes/*" &&
+ git fetch c &&
+ git branch --track local/a/master remotes/a/master &&
+ test "$(git config branch.local/a/master.remote)" = "c" &&
+ test "$(git config branch.local/a/master.merge)" = "refs/remotes/a/master" &&
+ git rev-parse --verify a >expect &&
+ git rev-parse --verify local/a/master >actual &&
+ test_cmp expect actual
+ )
+'
+
test_done
. "$TEST_DIRECTORY"/lib-rebase.sh
-set_fake_editor
-
# WARNING: Modifications to the initial repository can change the SHA ID used
# in the expect2 file for the 'stop on conflicting pick' test.
test_expect_success 'rebase -i with the exec command' '
git checkout master &&
(
+ set_fake_editor &&
FAKE_LINES="1 exec_>touch-one
2 exec_>touch-two exec_false exec_>touch-three
3 4 exec_>\"touch-file__name_with_spaces\";_>touch-after-semicolon 5" &&
test_expect_success 'rebase -i with the exec command runs from tree root' '
git checkout master &&
mkdir subdir && (cd subdir &&
+ set_fake_editor &&
FAKE_LINES="1 exec_>touch-subdir" \
git rebase -i HEAD^
) &&
test_expect_success 'rebase -i with the exec command checks tree cleanness' '
git checkout master &&
(
+ set_fake_editor &&
FAKE_LINES="exec_echo_foo_>file1 1" &&
export FAKE_LINES &&
test_must_fail git rebase -i HEAD^
git checkout master &&
test_when_finished "git rebase --abort" &&
(
+ set_fake_editor &&
FAKE_LINES="exec_this-command-does-not-exist 1" &&
export FAKE_LINES &&
test_must_fail git rebase -i HEAD^ >actual 2>&1
test_expect_success 'no changes are a nop' '
git checkout branch2 &&
+ set_fake_editor &&
git rebase -i F &&
test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch2" &&
test $(git rev-parse I) = $(git rev-parse HEAD)
git checkout -b dead-end &&
git rm file6 &&
git commit -m "stop here" &&
+ set_fake_editor &&
git rebase -i F branch2 &&
test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch2" &&
test $(git rev-parse I) = $(git rev-parse branch2) &&
test_expect_success 'test --onto <branch>' '
git checkout -b test-onto branch2 &&
+ set_fake_editor &&
git rebase -i --onto branch1 F &&
test "$(git symbolic-ref -q HEAD)" = "refs/heads/test-onto" &&
test $(git rev-parse HEAD^) = $(git rev-parse branch1) &&
test_expect_success 'rebase on top of a non-conflicting commit' '
git checkout branch1 &&
git tag original-branch1 &&
+ set_fake_editor &&
git rebase -i branch2 &&
test file6 = $(git diff --name-only original-branch1) &&
test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch1" &&
'
test_expect_success 'exchange two commits' '
+ set_fake_editor &&
FAKE_LINES="2 1" git rebase -i HEAD~2 &&
test H = $(git cat-file commit HEAD^ | sed -ne \$p) &&
test G = $(git cat-file commit HEAD | sed -ne \$p)
test_expect_success 'stop on conflicting pick' '
git tag new-branch1 &&
+ set_fake_editor &&
test_must_fail git rebase -i master &&
test "$(git rev-parse HEAD~3)" = "$(git rev-parse master)" &&
test_cmp expect .git/rebase-merge/patch &&
test_expect_success 'abort with error when new base cannot be checked out' '
git rm --cached file1 &&
git commit -m "remove file in base" &&
+ set_fake_editor &&
test_must_fail git rebase -i master > output 2>&1 &&
grep "The following untracked working tree files would be overwritten by checkout:" \
output &&
test_tick &&
GIT_AUTHOR_NAME="Twerp Snog" git commit -m "different author" &&
git tag twerp &&
+ set_fake_editor &&
git rebase -i --onto master HEAD^ &&
git show HEAD | grep "^Author: Twerp Snog"
'
test_tick &&
GIT_AUTHOR_NAME="Nitfol" git commit -m "nitfol" file7 &&
echo "******************************" &&
+ set_fake_editor &&
FAKE_LINES="1 squash 2" EXPECT_HEADER_COUNT=2 \
git rebase -i --onto master HEAD~2 &&
test B = $(cat file7) &&
test_expect_success '-p handles "no changes" gracefully' '
HEAD=$(git rev-parse HEAD) &&
+ set_fake_editor &&
git rebase -i -p HEAD^ &&
git update-index --refresh &&
git diff-files --quiet &&
test_expect_failure 'exchange two commits with -p' '
git checkout H &&
+ set_fake_editor &&
FAKE_LINES="2 1" git rebase -i -p HEAD~2 &&
test H = $(git cat-file commit HEAD^ | sed -ne \$p) &&
test G = $(git cat-file commit HEAD | sed -ne \$p)
git commit -m M file1 &&
git checkout -b to-be-rebased &&
test_tick &&
+ set_fake_editor &&
git rebase -i -p --onto branch1 master &&
git update-index --refresh &&
git diff-files --quiet &&
'
test_expect_success 'edit ancestor with -p' '
+ set_fake_editor &&
FAKE_LINES="1 2 edit 3 4" git rebase -i -p HEAD~3 &&
echo 2 > unrelated-file &&
test_tick &&
test_expect_success '--continue tries to commit' '
test_tick &&
+ set_fake_editor &&
test_must_fail git rebase -i --onto new-branch1 HEAD^ &&
echo resolved > file1 &&
git add file1 &&
test_expect_success 'verbose flag is heeded, even after --continue' '
git reset --hard master@{1} &&
test_tick &&
+ set_fake_editor &&
test_must_fail git rebase -v -i --onto new-branch1 HEAD^ &&
echo resolved > file1 &&
git add file1 &&
test_expect_success 'multi-squash only fires up editor once' '
base=$(git rev-parse HEAD~4) &&
+ set_fake_editor &&
FAKE_COMMIT_AMEND="ONCE" FAKE_LINES="1 squash 2 squash 3 squash 4" \
EXPECT_HEADER_COUNT=4 \
git rebase -i $base &&
test_expect_success 'multi-fixup does not fire up editor' '
git checkout -b multi-fixup E &&
base=$(git rev-parse HEAD~4) &&
+ set_fake_editor &&
FAKE_COMMIT_AMEND="NEVER" FAKE_LINES="1 fixup 2 fixup 3 fixup 4" \
git rebase -i $base &&
test $base = $(git rev-parse HEAD^) &&
test_expect_success 'commit message used after conflict' '
git checkout -b conflict-fixup conflict-branch &&
base=$(git rev-parse HEAD~4) &&
+ set_fake_editor &&
(
FAKE_LINES="1 fixup 3 fixup 4" &&
export FAKE_LINES &&
test_expect_success 'commit message retained after conflict' '
git checkout -b conflict-squash conflict-branch &&
base=$(git rev-parse HEAD~4) &&
+ set_fake_editor &&
(
FAKE_LINES="1 fixup 3 squash 4" &&
export FAKE_LINES &&
test_expect_success 'squash and fixup generate correct log messages' '
git checkout -b squash-fixup E &&
base=$(git rev-parse HEAD~4) &&
+ set_fake_editor &&
FAKE_COMMIT_AMEND="ONCE" FAKE_LINES="1 fixup 2 squash 3 fixup 4" \
EXPECT_HEADER_COUNT=4 \
git rebase -i $base &&
test_expect_success 'squash ignores comments' '
git checkout -b skip-comments E &&
base=$(git rev-parse HEAD~4) &&
+ set_fake_editor &&
FAKE_COMMIT_AMEND="ONCE" FAKE_LINES="# 1 # squash 2 # squash 3 # squash 4 #" \
EXPECT_HEADER_COUNT=4 \
git rebase -i $base &&
test_expect_success 'squash ignores blank lines' '
git checkout -b skip-blank-lines E &&
base=$(git rev-parse HEAD~4) &&
+ set_fake_editor &&
FAKE_COMMIT_AMEND="ONCE" FAKE_LINES="> 1 > squash 2 > squash 3 > squash 4 >" \
EXPECT_HEADER_COUNT=4 \
git rebase -i $base &&
test_expect_success 'squash works as expected' '
git checkout -b squash-works no-conflict-branch &&
one=$(git rev-parse HEAD~3) &&
+ set_fake_editor &&
FAKE_LINES="1 squash 3 2" EXPECT_HEADER_COUNT=2 \
git rebase -i HEAD~3 &&
test $one = $(git rev-parse HEAD~2)
test_expect_success 'interrupted squash works as expected' '
git checkout -b interrupted-squash conflict-branch &&
one=$(git rev-parse HEAD~3) &&
+ set_fake_editor &&
(
FAKE_LINES="1 squash 3 2" &&
export FAKE_LINES &&
test_expect_success 'interrupted squash works as expected (case 2)' '
git checkout -b interrupted-squash2 conflict-branch &&
one=$(git rev-parse HEAD~3) &&
+ set_fake_editor &&
(
FAKE_LINES="3 squash 1 2" &&
export FAKE_LINES &&
git commit -m "unrelated change" &&
parent=$(git rev-parse HEAD^) &&
test_tick &&
+ set_fake_editor &&
FAKE_LINES="edit 1" git rebase -i HEAD^ &&
echo edited > file7 &&
git add file7 &&
test_expect_success 'aborted --continue does not squash commits after "edit"' '
old=$(git rev-parse HEAD) &&
test_tick &&
+ set_fake_editor &&
FAKE_LINES="edit 1" git rebase -i HEAD^ &&
echo "edited again" > file7 &&
git add file7 &&
test_expect_success 'auto-amend only edited commits after "edit"' '
test_tick &&
+ set_fake_editor &&
FAKE_LINES="edit 1" git rebase -i HEAD^ &&
echo "edited again" > file7 &&
git add file7 &&
test_expect_success 'clean error after failed "exec"' '
test_tick &&
test_when_finished "git rebase --abort || :" &&
+ set_fake_editor &&
(
FAKE_LINES="1 exec_false" &&
export FAKE_LINES &&
grandparent=$(git rev-parse HEAD~2) &&
git checkout $(git rev-parse HEAD) &&
test_tick &&
+ set_fake_editor &&
FAKE_LINES="2 1" git rebase -i HEAD~2 &&
test $grandparent = $(git rev-parse HEAD~2)
'
test_must_fail git commit -m doesnt-verify file1 &&
git commit -m doesnt-verify --no-verify file1 &&
test_tick &&
+ set_fake_editor &&
FAKE_LINES=2 git rebase -i HEAD~2
'
git commit -m "Add body"
) &&
+ set_fake_editor &&
FAKE_LINES="1 squash 2" git rebase -i to-be-rebased &&
test "$(git show -s --pretty=format:%an)" = "Squashed Away"
GIT_EDITOR=: git commit --amend \
--author="Somebody else <somebody@else.com>" &&
test $(git rev-parse branch3) != $(git rev-parse branch4) &&
+ set_fake_editor &&
git rebase -i branch3 &&
test $(git rev-parse branch3) = $(git rev-parse branch4)
git commit -a -m "submodule second"
) &&
test_tick &&
+ set_fake_editor &&
git commit -a -m "Three changes submodule"
'
test_expect_success 'submodule rebase -i' '
+ set_fake_editor &&
FAKE_LINES="1 squash 2 3" git rebase -i A
'
'
test_expect_success 'rebase -i continue with only submodule staged' '
+ set_fake_editor &&
test_must_fail git rebase -i submodule-base &&
git add sub &&
git rebase --continue &&
test_expect_success 'rebase -i continue with unstaged submodule' '
git checkout submodule-topic &&
git reset --hard &&
+ set_fake_editor &&
test_must_fail git rebase -i submodule-base &&
git reset &&
git rebase --continue &&
test-chmtime =123456789 file3 &&
git update-index --refresh &&
HEAD=$(git rev-parse HEAD) &&
+ set_fake_editor &&
git rebase -i HEAD~4 &&
test $HEAD = $(git rev-parse HEAD) &&
MTIME=$(test-chmtime -v +0 file3 | sed 's/[^0-9].*$//') &&
test_expect_success 'reword' '
git checkout -b reword-branch master &&
+ set_fake_editor &&
FAKE_LINES="1 2 3 reword 4" FAKE_COMMIT_MESSAGE="E changed" git rebase -i A &&
git show HEAD | grep "E changed" &&
test $(git rev-parse master) != $(git rev-parse HEAD) &&
test_commit n2 &&
test_commit n3 &&
git notes add -m"a note" n3 &&
+ set_fake_editor &&
git rebase -i --onto n1 n2 &&
test "a note" = "$(git notes show HEAD)"
'
test_expect_success 'rebase -i can copy notes over a fixup' '
git reset --hard n3 &&
git notes add -m"an earlier note" n2 &&
+ set_fake_editor &&
GIT_NOTES_REWRITE_MODE=concatenate FAKE_LINES="1 fixup 2" git rebase -i n1 &&
git notes show > output &&
test_cmp expect output
git symbolic-ref HEAD &&
grandparent=$(git rev-parse HEAD~2) &&
test_tick &&
+ set_fake_editor &&
FAKE_LINES="2 1" git rebase -i HEAD~2 HEAD^0 &&
test $grandparent = $(git rev-parse HEAD~2) &&
test_must_fail git symbolic-ref HEAD
test_expect_success 'always cherry-pick with --no-ff' '
git checkout no-ff-branch &&
git tag original-no-ff-branch &&
+ set_fake_editor &&
git rebase -i --no-ff A &&
touch empty &&
for p in 0 1 2
test_expect_success 'rebase-i history with funny messages' '
git rev-list A..funny >expect &&
test_tick &&
+ set_fake_editor &&
FAKE_LINES="1 2 3 4" git rebase -i A &&
git rev-list A.. >actual &&
test_cmp expect actual
test_expect_success 'running "git rebase -i --exec git show HEAD"' '
+ set_fake_editor &&
git rebase -i --exec "git show HEAD" HEAD~2 >actual &&
(
FAKE_LINES="1 exec_git_show_HEAD 2 exec_git_show_HEAD" &&
test_expect_success 'running "git rebase --exec git show HEAD -i"' '
git reset --hard execute &&
+ set_fake_editor &&
git rebase --exec "git show HEAD" -i HEAD~2 >actual &&
(
FAKE_LINES="1 exec_git_show_HEAD 2 exec_git_show_HEAD" &&
test_expect_success 'running "git rebase -ix git show HEAD"' '
git reset --hard execute &&
+ set_fake_editor &&
git rebase -ix "git show HEAD" HEAD~2 >actual &&
(
FAKE_LINES="1 exec_git_show_HEAD 2 exec_git_show_HEAD" &&
test_expect_success 'rebase -ix with several <CMD>' '
git reset --hard execute &&
+ set_fake_editor &&
git rebase -ix "git show HEAD; pwd" HEAD~2 >actual &&
(
FAKE_LINES="1 exec_git_show_HEAD;_pwd 2 exec_git_show_HEAD;_pwd" &&
test_expect_success 'rebase -ix with several instances of --exec' '
git reset --hard execute &&
+ set_fake_editor &&
git rebase -i --exec "git show HEAD" --exec "pwd" HEAD~2 >actual &&
(
FAKE_LINES="1 exec_git_show_HEAD exec_pwd 2
echo bis >bis.txt &&
git add bis.txt &&
git commit -m "fixup! two_exec" &&
+ set_fake_editor &&
(
git checkout -b autosquash_actual &&
git rebase -i --exec "git show HEAD" --autosquash HEAD~4 >actual
test_expect_success 'rebase --exec without -i shows error message' '
git reset --hard execute &&
+ set_fake_editor &&
test_must_fail git rebase --exec "git show HEAD" HEAD~2 2>actual &&
echo "The --exec option must be used with the --interactive option" >expected &&
test_i18ncmp expected actual
test_expect_success 'rebase -i --exec without <CMD>' '
git reset --hard execute &&
+ set_fake_editor &&
test_must_fail git rebase -i --exec 2>tmp &&
sed -e "1d" tmp >actual &&
test_must_fail git rebase -h >expected &&
test_expect_success 'rebase -i --root re-order and drop commits' '
git checkout E &&
+ set_fake_editor &&
FAKE_LINES="3 1 2 5" git rebase -i --root &&
test E = $(git cat-file commit HEAD | sed -ne \$p) &&
test B = $(git cat-file commit HEAD^ | sed -ne \$p) &&
echo B >file7 &&
git add file7 &&
GIT_AUTHOR_NAME="Twerp Snog" git commit -m "different author" &&
+ set_fake_editor &&
FAKE_LINES="2" git rebase -i --root &&
git cat-file commit HEAD | grep -q "^author Twerp Snog" &&
git cat-file commit HEAD | grep -q "^different author$"
test_expect_success 'rebase -i --root temporary sentinel commit' '
git checkout B &&
(
+ set_fake_editor &&
FAKE_LINES="2" &&
export FAKE_LINES &&
test_must_fail git rebase -i --root
test_expect_success 'rebase -i --root fixup root commit' '
git checkout B &&
+ set_fake_editor &&
FAKE_LINES="1 fixup 2" git rebase -i --root &&
test A = $(git cat-file commit HEAD | sed -ne \$p) &&
test B = $(git show HEAD:file1) &&
test_expect_success 'rebase --edit-todo does not works on non-interactive rebase' '
git reset --hard &&
git checkout conflict-branch &&
+ set_fake_editor &&
test_must_fail git rebase --onto HEAD~2 HEAD~ &&
test_must_fail git rebase --edit-todo &&
git rebase --abort
test_expect_success 'rebase --edit-todo can be used to modify todo' '
git reset --hard &&
git checkout no-conflict-branch^0 &&
+ set_fake_editor &&
FAKE_LINES="edit 1 2 3" git rebase -i HEAD~3 &&
FAKE_LINES="2 1" git rebase --edit-todo &&
git rebase --continue
test_expect_success 'rebase -i produces readable reflog' '
git reset --hard &&
git branch -f branch-reflog-test H &&
+ set_fake_editor &&
git rebase -i --onto I F branch-reflog-test &&
cat >expect <<-\EOF &&
rebase -i (start): checkout I
test_expect_code 1 grep " emp" error
'
+test_expect_success 'short SHA-1 setup' '
+ test_when_finished "git checkout master" &&
+ git checkout --orphan collide &&
+ git rm -rf . &&
+ (
+ unset test_tick &&
+ test_commit collide1 collide &&
+ test_commit --notick collide2 collide &&
+ test_commit --notick collide3 collide
+ )
+'
+
+test_expect_success 'short SHA-1 collide' '
+ test_when_finished "reset_rebase && git checkout master" &&
+ git checkout collide &&
+ (
+ unset test_tick &&
+ test_tick &&
+ set_fake_editor &&
+ FAKE_COMMIT_MESSAGE="collide2 ac4f2ee" \
+ FAKE_LINES="reword 1 2" git rebase -i HEAD~2
+ )
+'
+
test_done
# \--A3 <-- topic2
# \
# B2 <-- origin/topic
+#
+# Clone 4 (same as Clone 3)
test_expect_success 'setup for merge-preserving rebase' \
'echo First > A &&
git merge --no-ff topic2
) &&
+ git clone ./. clone4 &&
+ (
+ cd clone4 &&
+ git checkout -b topic2 origin/topic &&
+ echo Sixth > A &&
+ git commit -a -m "Modify A3" &&
+ git checkout -b topic origin/topic &&
+ git merge --no-ff topic2
+ ) &&
+
git checkout topic &&
echo Fourth >> B &&
git commit -a -m "Modify B2"
)
'
+test_expect_success 'rebase -p ignores merge.log config' '
+ (
+ cd clone4 &&
+ git fetch &&
+ git -c merge.log=1 rebase -p origin/topic &&
+ echo >expected &&
+ git log --format="%b" -1 >current &&
+ test_cmp expected current
+ )
+'
+
test_done
'
-test_expect_success 'chery-pick on unborn branch' '
+test_expect_success 'cherry-pick on unborn branch' '
git checkout --orphan unborn &&
git rm --cached -r . &&
rm -rf * &&
! test_cmp_rev initial HEAD
'
+test_expect_success 'cherry-pick "-" to pick from previous branch' '
+ git checkout unborn &&
+ test_commit to-pick actual content &&
+ git checkout master &&
+ git cherry-pick - &&
+ echo content >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'cherry-pick "-" is meaningless without checkout' '
+ test_create_repo afresh &&
+ (
+ cd afresh &&
+ test_commit one &&
+ test_commit two &&
+ test_commit three &&
+ test_must_fail git cherry-pick -
+ )
+'
+
test_done
test "$(git rev-parse --verify HEAD)" = "1df192cd8bc58a2b275d842cede4d221ad9000d1"
'
-test_expect_success 'chery-pick --ff on unborn branch' '
+test_expect_success 'cherry-pick --ff on unborn branch' '
git checkout --orphan unborn &&
git rm --cached -r . &&
rm -rf * &&
echo content > sub/file &&
echo foo > othersub/whatever &&
git add -A &&
- git commit -m "Common commmit" &&
+ git commit -m "Common commit" &&
git rm -rf othersub &&
git mv sub/file othersub &&
'
cat >expect <<EOF
+M .gitmodules
D submod
EOF
M submod
EOF
+cat >expect.cached <<EOF
+D submod
+EOF
+
+cat >expect.both_deleted<<EOF
+D .gitmodules
+D submod
+EOF
+
test_expect_success 'rm removes empty submodules from work tree' '
mkdir submod &&
git update-index --add --cacheinfo 160000 $(git rev-parse HEAD) submod &&
git rm submod &&
test ! -e submod &&
git status -s -uno --ignore-submodules=none > actual &&
- test_cmp expect actual
+ test_cmp expect actual &&
+ test_must_fail git config -f .gitmodules submodule.sub.url &&
+ test_must_fail git config -f .gitmodules submodule.sub.path
'
-test_expect_success 'rm removes removed submodule from index' '
+test_expect_success 'rm removes removed submodule from index and .gitmodules' '
git reset --hard &&
git submodule update &&
rm -rf submod &&
git rm submod &&
git status -s -uno --ignore-submodules=none > actual &&
- test_cmp expect actual
+ test_cmp expect actual &&
+ test_must_fail git config -f .gitmodules submodule.sub.url &&
+ test_must_fail git config -f .gitmodules submodule.sub.path
'
test_expect_success 'rm removes work tree of unmodified submodules' '
git rm submod &&
test ! -d submod &&
git status -s -uno --ignore-submodules=none > actual &&
- test_cmp expect actual
+ test_cmp expect actual &&
+ test_must_fail git config -f .gitmodules submodule.sub.url &&
+ test_must_fail git config -f .gitmodules submodule.sub.path
'
test_expect_success 'rm removes a submodule with a trailing /' '
git rm -f submod &&
test ! -d submod &&
git status -s -uno --ignore-submodules=none > actual &&
+ test_cmp expect actual &&
+ test_must_fail git config -f .gitmodules submodule.sub.url &&
+ test_must_fail git config -f .gitmodules submodule.sub.path
+'
+
+test_expect_success 'rm --cached leaves work tree of populated submodules and .gitmodules alone' '
+ git reset --hard &&
+ git submodule update &&
+ git rm --cached submod &&
+ test -d submod &&
+ test -f submod/.git &&
+ git status -s -uno >actual &&
+ test_cmp expect.cached actual &&
+ git config -f .gitmodules submodule.sub.url &&
+ git config -f .gitmodules submodule.sub.path
+'
+
+test_expect_success 'rm --dry-run does not touch the submodule or .gitmodules' '
+ git reset --hard &&
+ git submodule update &&
+ git rm -n submod &&
+ test -f submod/.git &&
+ git diff-index --exit-code HEAD
+'
+
+test_expect_success 'rm does not complain when no .gitmodules file is found' '
+ git reset --hard &&
+ git submodule update &&
+ git rm .gitmodules &&
+ git rm submod >actual 2>actual.err &&
+ ! test -s actual.err &&
+ ! test -d submod &&
+ ! test -f submod/.git &&
+ git status -s -uno >actual &&
+ test_cmp expect.both_deleted actual
+'
+
+test_expect_success 'rm will error out on a modified .gitmodules file unless staged' '
+ git reset --hard &&
+ git submodule update &&
+ git config -f .gitmodules foo.bar true &&
+ test_must_fail git rm submod >actual 2>actual.err &&
+ test -s actual.err &&
+ test -d submod &&
+ test -f submod/.git &&
+ git diff-files --quiet -- submod &&
+ git add .gitmodules &&
+ git rm submod >actual 2>actual.err &&
+ ! test -s actual.err &&
+ ! test -d submod &&
+ ! test -f submod/.git &&
+ git status -s -uno >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'rm issues a warning when section is not found in .gitmodules' '
+ git reset --hard &&
+ git submodule update &&
+ git config -f .gitmodules --remove-section submodule.sub &&
+ git add .gitmodules &&
+ echo "warning: Could not find section in .gitmodules where path=submod" >expect.err &&
+ git rm submod >actual 2>actual.err &&
+ test_i18ncmp expect.err actual.err &&
+ ! test -d submod &&
+ ! test -f submod/.git &&
+ git status -s -uno >actual &&
test_cmp expect actual
'
git rm -f submod &&
test ! -d submod &&
git status -s -uno --ignore-submodules=none > actual &&
- test_cmp expect actual
+ test_cmp expect actual &&
+ test_must_fail git config -f .gitmodules submodule.sub.url &&
+ test_must_fail git config -f .gitmodules submodule.sub.path
'
test_expect_success 'rm of a conflicted populated submodule with modifications fails unless forced' '
git rm -f submod &&
test ! -d submod &&
git status -s -uno --ignore-submodules=none > actual &&
- test_cmp expect actual
+ test_cmp expect actual &&
+ test_must_fail git config -f .gitmodules submodule.sub.url &&
+ test_must_fail git config -f .gitmodules submodule.sub.path
'
test_expect_success 'rm of a conflicted populated submodule with untracked files fails unless forced' '
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-prereq-FILEMODE.sh
-test_expect_success PERL 'setup (initial)' '
+if ! test_have_prereq PERL
+then
+ skip_all='skipping add -i tests, perl not available'
+ test_done
+fi
+
+test_expect_success 'setup (initial)' '
echo content >file &&
git add file &&
echo more >>file &&
echo lines >>file
'
-test_expect_success PERL 'status works (initial)' '
+test_expect_success 'status works (initial)' '
git add -i </dev/null >output &&
grep "+1/-0 *+2/-0 file" output
'
-test_expect_success PERL 'setup expected' '
+test_expect_success 'setup expected' '
cat >expected <<EOF
new file mode 100644
index 0000000..d95f3ad
EOF
'
-test_expect_success PERL 'diff works (initial)' '
+test_expect_success 'diff works (initial)' '
(echo d; echo 1) | git add -i >output &&
sed -ne "/new file/,/content/p" <output >diff &&
test_cmp expected diff
'
-test_expect_success PERL 'revert works (initial)' '
+test_expect_success 'revert works (initial)' '
git add file &&
(echo r; echo 1) | git add -i &&
git ls-files >output &&
! grep . output
'
-test_expect_success PERL 'setup (commit)' '
+test_expect_success 'setup (commit)' '
echo baseline >file &&
git add file &&
git commit -m commit &&
echo more >>file &&
echo lines >>file
'
-test_expect_success PERL 'status works (commit)' '
+test_expect_success 'status works (commit)' '
git add -i </dev/null >output &&
grep "+1/-0 *+2/-0 file" output
'
-test_expect_success PERL 'setup expected' '
+test_expect_success 'setup expected' '
cat >expected <<EOF
index 180b47c..b6f2c08 100644
--- a/file
EOF
'
-test_expect_success PERL 'diff works (commit)' '
+test_expect_success 'diff works (commit)' '
(echo d; echo 1) | git add -i >output &&
sed -ne "/^index/,/content/p" <output >diff &&
test_cmp expected diff
'
-test_expect_success PERL 'revert works (commit)' '
+test_expect_success 'revert works (commit)' '
git add file &&
(echo r; echo 1) | git add -i &&
git add -i </dev/null >output &&
'
-test_expect_success PERL 'setup expected' '
+test_expect_success 'setup expected' '
cat >expected <<EOF
EOF
'
-test_expect_success PERL 'setup fake editor' '
+test_expect_success 'setup fake editor' '
>fake_editor.sh &&
chmod a+x fake_editor.sh &&
test_set_editor "$(pwd)/fake_editor.sh"
'
-test_expect_success PERL 'dummy edit works' '
+test_expect_success 'dummy edit works' '
(echo e; echo a) | git add -p &&
git diff > diff &&
test_cmp expected diff
'
-test_expect_success PERL 'setup patch' '
+test_expect_success 'setup patch' '
cat >patch <<EOF
@@ -1,1 +1,4 @@
this
EOF
'
-test_expect_success PERL 'setup fake editor' '
+test_expect_success 'setup fake editor' '
echo "#!$SHELL_PATH" >fake_editor.sh &&
cat >>fake_editor.sh <<\EOF &&
mv -f "$1" oldpatch &&
test_set_editor "$(pwd)/fake_editor.sh"
'
-test_expect_success PERL 'bad edit rejected' '
+test_expect_success 'bad edit rejected' '
git reset &&
(echo e; echo n; echo d) | git add -p >output &&
grep "hunk does not apply" output
'
-test_expect_success PERL 'setup patch' '
+test_expect_success 'setup patch' '
cat >patch <<EOF
this patch
is garbage
EOF
'
-test_expect_success PERL 'garbage edit rejected' '
+test_expect_success 'garbage edit rejected' '
git reset &&
(echo e; echo n; echo d) | git add -p >output &&
grep "hunk does not apply" output
'
-test_expect_success PERL 'setup patch' '
+test_expect_success 'setup patch' '
cat >patch <<EOF
@@ -1,0 +1,0 @@
baseline
EOF
'
-test_expect_success PERL 'setup expected' '
+test_expect_success 'setup expected' '
cat >expected <<EOF
diff --git a/file b/file
index b5dd6c9..f910ae9 100644
EOF
'
-test_expect_success PERL 'real edit works' '
+test_expect_success 'real edit works' '
(echo e; echo n; echo d) | git add -p &&
git diff >output &&
test_cmp expected output
'
-test_expect_success PERL 'skip files similarly as commit -a' '
+test_expect_success 'skip files similarly as commit -a' '
git reset &&
echo file >.gitignore &&
echo changed >file &&
'
rm -f .gitignore
-test_expect_success PERL,FILEMODE 'patch does not affect mode' '
+test_expect_success FILEMODE 'patch does not affect mode' '
git reset --hard &&
echo content >>file &&
chmod +x file &&
git diff file | grep "new mode"
'
-test_expect_success PERL,FILEMODE 'stage mode but not hunk' '
+test_expect_success FILEMODE 'stage mode but not hunk' '
git reset --hard &&
echo content >>file &&
chmod +x file &&
'
-test_expect_success PERL,FILEMODE 'stage mode and hunk' '
+test_expect_success FILEMODE 'stage mode and hunk' '
git reset --hard &&
echo content >>file &&
chmod +x file &&
# end of tests disabled when filemode is not usable
-test_expect_success PERL 'setup again' '
+test_expect_success 'setup again' '
git reset --hard &&
test_chmod +x file &&
echo content >>file
'
# Write the patch file with a new line at the top and bottom
-test_expect_success PERL 'setup patch' '
+test_expect_success 'setup patch' '
cat >patch <<EOF
index 180b47c..b6f2c08 100644
--- a/file
'
# Expected output, similar to the patch but w/ diff at the top
-test_expect_success PERL 'setup expected' '
+test_expect_success 'setup expected' '
cat >expected <<EOF
diff --git a/file b/file
index b6f2c08..61b9053 100755
'
# Test splitting the first patch, then adding both
-test_expect_success PERL 'add first line works' '
+test_expect_success 'add first line works' '
git commit -am "clear local changes" &&
git apply patch &&
(echo s; echo y; echo y) | git add -p file &&
test_cmp expected diff
'
-test_expect_success PERL 'setup expected' '
+test_expect_success 'setup expected' '
cat >expected <<EOF
diff --git a/non-empty b/non-empty
deleted file mode 100644
EOF
'
-test_expect_success PERL 'deleting a non-empty file' '
+test_expect_success 'deleting a non-empty file' '
git reset --hard &&
echo content >non-empty &&
git add non-empty &&
test_cmp expected diff
'
-test_expect_success PERL 'setup expected' '
+test_expect_success 'setup expected' '
cat >expected <<EOF
diff --git a/empty b/empty
deleted file mode 100644
EOF
'
-test_expect_success PERL 'deleting an empty file' '
+test_expect_success 'deleting an empty file' '
git reset --hard &&
> empty &&
git add empty &&
test_cmp expected diff
'
-test_expect_success PERL 'split hunk setup' '
+test_expect_success 'split hunk setup' '
git reset --hard &&
for i in 10 20 30 40 50 60
do
done >test
'
-test_expect_success PERL 'split hunk "add -p (edit)"' '
+test_expect_success 'split hunk "add -p (edit)"' '
# Split, say Edit and do nothing. Then:
#
# 1. Broken version results in a patch that does not apply and
test_expect_success "detect if nfd needed" '
precomposeunicode=`git config core.precomposeunicode` &&
- test "$precomposeunicode" = false &&
+ test "$precomposeunicode" = true &&
git config core.precomposeunicode true
'
test_expect_success "setup" '
test_expect_success 'non-integer config parsing' '
git config diff.context no &&
test_must_fail git diff 2>output &&
- test_i18ngrep "bad config value" output
+ test_i18ngrep "bad numeric config value" output
'
test_expect_success 'negative integer config parsing' '
Blob Guy <author@example.com>
Blob Guy <bugs@company.xx>
EOF
- git add just-bugs both &&
+ printf "Tricky Guy <author@example.com>" >no-newline &&
+ git add just-bugs both no-newline &&
git commit -m "my mailmaps" &&
echo "Repo Guy <author@example.com>" >.mailmap &&
echo "Internal Guy <author@example.com>" >internal.map
)
'
+test_expect_success 'mailmap.blob can handle blobs without trailing newline' '
+ cat >expect <<-\EOF &&
+ Tricky Guy (1):
+ initial
+
+ nick1 (1):
+ second
+
+ EOF
+ git -c mailmap.blob=map:no-newline shortlog HEAD >actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'cleanup after mailmap.blob tests' '
rm -f .mailmap
'
test_cmp expect actual.fuzz
'
+cat >expect <<\EOF
+Some Dude <some@dude.xx>
+EOF
+
+test_expect_success 'commit --author honors mailmap' '
+ test_must_fail git commit --author "nick" --allow-empty -meight &&
+ git commit --author "Some Dude" --allow-empty -meight &&
+ git show --pretty=format:"%an <%ae>%n" >actual &&
+ test_cmp expect actual
+'
+
test_done
canned_test "-M -L ':f:b.c' parallel-change" parallel-change-f-to-main
canned_test "-L 4,12:a.c -L :main:a.c simple" multiple
-canned_test "-L 4,18:a.c -L :main:a.c simple" multiple-overlapping
+canned_test "-L 4,18:a.c -L ^:main:a.c simple" multiple-overlapping
canned_test "-L :main:a.c -L 4,18:a.c simple" multiple-overlapping
canned_test "-L 4:a.c -L 8,12:a.c simple" multiple-superset
canned_test "-L 8,12:a.c -L 4:a.c simple" multiple-superset
test_bad_opts "-L :b.c" "argument.*not of the form"
test_bad_opts "-L :foo:b.c" "no match"
-# There is a separate bug when an empty -L range is the first -L encountered,
-# thus to demonstrate this particular bug, the empty -L range must follow a
-# non-empty -L range.
-test_expect_success '-L {empty-range} (any -L)' '
+test_expect_success '-L X (X == nlines)' '
+ n=$(wc -l <b.c) &&
+ git log -L $n:b.c
+'
+
+test_expect_success '-L X (X == nlines + 1)' '
n=$(expr $(wc -l <b.c) + 1) &&
- git log -L1,1:b.c -L$n:b.c
+ test_must_fail git log -L $n:b.c
+'
+
+test_expect_success '-L X (X == nlines + 2)' '
+ n=$(expr $(wc -l <b.c) + 2) &&
+ test_must_fail git log -L $n:b.c
'
-test_expect_success '-L {empty-range} (first -L)' '
+test_expect_success '-L ,Y (Y == nlines)' '
+ n=$(printf "%d" $(wc -l <b.c)) &&
+ git log -L ,$n:b.c
+'
+
+test_expect_success '-L ,Y (Y == nlines + 1)' '
n=$(expr $(wc -l <b.c) + 1) &&
- git log -L$n:b.c
+ test_must_fail git log -L ,$n:b.c
+'
+
+test_expect_success '-L ,Y (Y == nlines + 2)' '
+ n=$(expr $(wc -l <b.c) + 2) &&
+ test_must_fail git log -L ,$n:b.c
'
test_done
--- /dev/null
+#!/bin/sh
+
+test_description='handling of duplicate objects in incoming packfiles'
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-pack.sh
+
+# The sha1s we have in our pack. It's important that these have the same
+# starting byte, so that they end up in the same fanout section of the index.
+# That lets us make sure we are exercising the binary search with both sets.
+LO_SHA1=e68fe8129b546b101aee9510c5328e7f21ca1d18
+HI_SHA1=e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
+
+# And here's a "missing sha1" which will produce failed lookups. It must also
+# be in the same fanout section, and should be between the two (so that during
+# our binary search, we are sure to end up looking at one or the other of the
+# duplicate runs).
+MISSING_SHA1='e69d000000000000000000000000000000000000'
+
+# git will never intentionally create packfiles with
+# duplicate objects, so we have to construct them by hand.
+#
+# $1 is the name of the packfile to create
+#
+# $2 is the number of times to duplicate each object
+create_pack () {
+ pack_header "$((2 * $2))" >"$1" &&
+ for i in $(test_seq 1 "$2"); do
+ pack_obj $LO_SHA1 &&
+ pack_obj $HI_SHA1
+ done >>"$1" &&
+ pack_trailer "$1"
+}
+
+# double-check that create_pack actually works
+test_expect_success 'pack with no duplicates' '
+ create_pack no-dups.pack 1 &&
+ git index-pack --stdin <no-dups.pack
+'
+
+test_expect_success 'index-pack will allow duplicate objects by default' '
+ clear_packs &&
+ create_pack dups.pack 100 &&
+ git index-pack --stdin <dups.pack
+'
+
+test_expect_success 'create batch-check test vectors' '
+ cat >input <<-EOF &&
+ $LO_SHA1
+ $HI_SHA1
+ $MISSING_SHA1
+ EOF
+ cat >expect <<-EOF
+ $LO_SHA1 blob 2
+ $HI_SHA1 blob 0
+ $MISSING_SHA1 missing
+ EOF
+'
+
+test_expect_success 'lookup in duplicated pack (binary search)' '
+ git cat-file --batch-check <input >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'lookup in duplicated pack (GIT_USE_LOOKUP)' '
+ (
+ GIT_USE_LOOKUP=1 &&
+ export GIT_USE_LOOKUP &&
+ git cat-file --batch-check <input >actual
+ ) &&
+ test_cmp expect actual
+'
+
+test_expect_success 'index-pack can reject packs with duplicates' '
+ clear_packs &&
+ create_pack dups.pack 2 &&
+ test_must_fail git index-pack --strict --stdin <dups.pack &&
+ test_expect_code 1 git cat-file -e $LO_SHA1
+'
+
+test_done
--- /dev/null
+#!/bin/sh
+
+test_description='test index-pack handling of delta cycles in packfiles'
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-pack.sh
+
+# Two similar-ish objects that we have computed deltas between.
+A=01d7713666f4de822776c7622c10f1b07de280dc
+B=e68fe8129b546b101aee9510c5328e7f21ca1d18
+
+# double-check our hand-constucted packs
+test_expect_success 'index-pack works with a single delta (A->B)' '
+ clear_packs &&
+ {
+ pack_header 2 &&
+ pack_obj $A $B &&
+ pack_obj $B
+ } >ab.pack &&
+ pack_trailer ab.pack &&
+ git index-pack --stdin <ab.pack &&
+ git cat-file -t $A &&
+ git cat-file -t $B
+'
+
+test_expect_success 'index-pack works with a single delta (B->A)' '
+ clear_packs &&
+ {
+ pack_header 2 &&
+ pack_obj $A &&
+ pack_obj $B $A
+ } >ba.pack &&
+ pack_trailer ba.pack &&
+ git index-pack --stdin <ba.pack &&
+ git cat-file -t $A &&
+ git cat-file -t $B
+'
+
+test_expect_success 'index-pack detects missing base objects' '
+ clear_packs &&
+ {
+ pack_header 1 &&
+ pack_obj $A $B
+ } >missing.pack &&
+ pack_trailer missing.pack &&
+ test_must_fail git index-pack --fix-thin --stdin <missing.pack
+'
+
+test_expect_success 'index-pack detects REF_DELTA cycles' '
+ clear_packs &&
+ {
+ pack_header 2 &&
+ pack_obj $A $B &&
+ pack_obj $B $A
+ } >cycle.pack &&
+ pack_trailer cycle.pack &&
+ test_must_fail git index-pack --fix-thin --stdin <cycle.pack
+'
+
+test_expect_failure 'failover to an object in another pack' '
+ clear_packs &&
+ git index-pack --stdin <ab.pack &&
+ git index-pack --stdin --fix-thin <cycle.pack
+'
+
+test_expect_failure 'failover to a duplicate object in the same pack' '
+ clear_packs &&
+ {
+ pack_header 3 &&
+ pack_obj $A $B &&
+ pack_obj $B $A &&
+ pack_obj $A
+ } >recoverable.pack &&
+ pack_trailer recoverable.pack &&
+ git index-pack --fix-thin --stdin <recoverable.pack
+'
+
+test_done
git fsck --no-dangling
)
'
+test_expect_success 'fetch creating new shallow root' '
+ (
+ git clone "file://$(pwd)/." shallow10 &&
+ git commit --allow-empty -m empty &&
+ cd shallow10 &&
+ git fetch --depth=1 --progress 2>actual &&
+ # This should fetch only the empty commit, no tree or
+ # blob objects
+ grep "remote: Total 1" actual
+ )
+'
test_expect_success 'setup tests for the --stdin parameter' '
for head in C D E F
)
'
+# configured prune tests
+
+set_config_tristate () {
+ # var=$1 val=$2
+ case "$2" in
+ unset) test_unconfig "$1" ;;
+ *) git config "$1" "$2" ;;
+ esac
+}
+
+test_configured_prune () {
+ fetch_prune=$1 remote_origin_prune=$2 cmdline=$3 expected=$4
+
+ test_expect_success "prune fetch.prune=$1 remote.origin.prune=$2${3:+ $3}; $4" '
+ # make sure a newbranch is there in . and also in one
+ git branch -f newbranch &&
+ (
+ cd one &&
+ test_unconfig fetch.prune &&
+ test_unconfig remote.origin.prune &&
+ git fetch &&
+ git rev-parse --verify refs/remotes/origin/newbranch
+ )
+
+ # now remove it
+ git branch -d newbranch &&
+
+ # then test
+ (
+ cd one &&
+ set_config_tristate fetch.prune $fetch_prune &&
+ set_config_tristate remote.origin.prune $remote_origin_prune &&
+
+ git fetch $cmdline &&
+ case "$expected" in
+ pruned)
+ test_must_fail git rev-parse --verify refs/remotes/origin/newbranch
+ ;;
+ kept)
+ git rev-parse --verify refs/remotes/origin/newbranch
+ ;;
+ esac
+ )
+ '
+}
+
+test_configured_prune unset unset "" kept
+test_configured_prune unset unset "--no-prune" kept
+test_configured_prune unset unset "--prune" pruned
+
+test_configured_prune false unset "" kept
+test_configured_prune false unset "--no-prune" kept
+test_configured_prune false unset "--prune" pruned
+
+test_configured_prune true unset "" pruned
+test_configured_prune true unset "--prune" pruned
+test_configured_prune true unset "--no-prune" kept
+
+test_configured_prune unset false "" kept
+test_configured_prune unset false "--no-prune" kept
+test_configured_prune unset false "--prune" pruned
+
+test_configured_prune false false "" kept
+test_configured_prune false false "--no-prune" kept
+test_configured_prune false false "--prune" pruned
+
+test_configured_prune true false "" kept
+test_configured_prune true false "--prune" pruned
+test_configured_prune true false "--no-prune" kept
+
+test_configured_prune unset true "" pruned
+test_configured_prune unset true "--no-prune" kept
+test_configured_prune unset true "--prune" pruned
+
+test_configured_prune false true "" pruned
+test_configured_prune false true "--no-prune" kept
+test_configured_prune false true "--prune" pruned
+
+test_configured_prune true true "" pruned
+test_configured_prune true true "--prune" pruned
+test_configured_prune true true "--no-prune" kept
+
test_expect_success 'all boundary commits are excluded' '
test_commit base &&
test_commit oneside &&
test_cmp expect actual
'
+test_expect_success 'push --no-thin must produce non-thin pack' '
+ cat >>path1 <<\EOF &&
+keep base version of path1 big enough, compared to the new changes
+later, in order to pass size heuristics in
+builtin/pack-objects.c:try_delta()
+EOF
+ git commit -am initial &&
+ git init no-thin &&
+ git --git-dir=no-thin/.git config receive.unpacklimit 0 &&
+ git push no-thin/.git refs/heads/master:refs/heads/foo &&
+ echo modified >> path1 &&
+ git commit -am modified &&
+ git repack -adf &&
+ rcvpck="git receive-pack --reject-thin-pack-for-testing" &&
+ git push --no-thin --receive-pack="$rcvpck" no-thin/.git refs/heads/master:refs/heads/foo
+'
+
test_done
test new = $(git show HEAD:file2)
'
+# add a feature branch, keep-merge, that is merged into master, so the
+# test can try preserving the merge commit (or not) with various
+# --rebase flags/pull.rebase settings.
+test_expect_success 'preserve merge setup' '
+ git reset --hard before-rebase &&
+ git checkout -b keep-merge second^ &&
+ test_commit file3 &&
+ git checkout to-rebase &&
+ git merge keep-merge &&
+ git tag before-preserve-rebase
+'
+
+test_expect_success 'pull.rebase=false create a new merge commit' '
+ git reset --hard before-preserve-rebase &&
+ test_config pull.rebase false &&
+ git pull . copy &&
+ test $(git rev-parse HEAD^1) = $(git rev-parse before-preserve-rebase) &&
+ test $(git rev-parse HEAD^2) = $(git rev-parse copy) &&
+ test file3 = $(git show HEAD:file3.t)
+'
+
+test_expect_success 'pull.rebase=true flattens keep-merge' '
+ git reset --hard before-preserve-rebase &&
+ test_config pull.rebase true &&
+ git pull . copy &&
+ test $(git rev-parse HEAD^^) = $(git rev-parse copy) &&
+ test file3 = $(git show HEAD:file3.t)
+'
+
+test_expect_success 'pull.rebase=1 is treated as true and flattens keep-merge' '
+ git reset --hard before-preserve-rebase &&
+ test_config pull.rebase 1 &&
+ git pull . copy &&
+ test $(git rev-parse HEAD^^) = $(git rev-parse copy) &&
+ test file3 = $(git show HEAD:file3.t)
+'
+
+test_expect_success 'pull.rebase=preserve rebases and merges keep-merge' '
+ git reset --hard before-preserve-rebase &&
+ test_config pull.rebase preserve &&
+ git pull . copy &&
+ test $(git rev-parse HEAD^^) = $(git rev-parse copy) &&
+ test $(git rev-parse HEAD^2) = $(git rev-parse keep-merge)
+'
+
+test_expect_success 'pull.rebase=invalid fails' '
+ git reset --hard before-preserve-rebase &&
+ test_config pull.rebase invalid &&
+ ! git pull . copy
+'
+
+test_expect_success '--rebase=false create a new merge commit' '
+ git reset --hard before-preserve-rebase &&
+ test_config pull.rebase true &&
+ git pull --rebase=false . copy &&
+ test $(git rev-parse HEAD^1) = $(git rev-parse before-preserve-rebase) &&
+ test $(git rev-parse HEAD^2) = $(git rev-parse copy) &&
+ test file3 = $(git show HEAD:file3.t)
+'
+
+test_expect_success '--rebase=true rebases and flattens keep-merge' '
+ git reset --hard before-preserve-rebase &&
+ test_config pull.rebase preserve &&
+ git pull --rebase=true . copy &&
+ test $(git rev-parse HEAD^^) = $(git rev-parse copy) &&
+ test file3 = $(git show HEAD:file3.t)
+'
+
+test_expect_success '--rebase=preserve rebases and merges keep-merge' '
+ git reset --hard before-preserve-rebase &&
+ test_config pull.rebase true &&
+ git pull --rebase=preserve . copy &&
+ test $(git rev-parse HEAD^^) = $(git rev-parse copy) &&
+ test $(git rev-parse HEAD^2) = $(git rev-parse keep-merge)
+'
+
+test_expect_success '--rebase=invalid fails' '
+ git reset --hard before-preserve-rebase &&
+ ! git pull --rebase=invalid . copy
+'
+
+test_expect_success '--rebase overrides pull.rebase=preserve and flattens keep-merge' '
+ git reset --hard before-preserve-rebase &&
+ test_config pull.rebase preserve &&
+ git pull --rebase . copy &&
+ test $(git rev-parse HEAD^^) = $(git rev-parse copy) &&
+ test file3 = $(git show HEAD:file3.t)
+'
+
test_expect_success '--rebase with rebased upstream' '
git remote add -f me . &&
printf "0032want %s\n0034shallow %s00000009done\n0000" \
$(git rev-parse HEAD) $(git rev-parse HEAD^) >input &&
test_must_fail git upload-pack . <input >/dev/null 2>output.err &&
- # pack-objects survived
- grep "Total.*, reused" output.err &&
- # but there was an error, which must have been in rev-list
grep "bad tree object" output.err
'
--- /dev/null
+#!/bin/sh
+
+test_description='compare & swap push force/delete safety'
+
+. ./test-lib.sh
+
+setup_srcdst_basic () {
+ rm -fr src dst &&
+ git clone --no-local . src &&
+ git clone --no-local src dst &&
+ (
+ cd src && git checkout HEAD^0
+ )
+}
+
+test_expect_success setup '
+ : create template repository
+ test_commit A &&
+ test_commit B &&
+ test_commit C
+'
+
+test_expect_success 'push to update (protected)' '
+ setup_srcdst_basic &&
+ (
+ cd dst &&
+ test_commit D &&
+ test_must_fail git push --force-with-lease=master:master origin master
+ ) &&
+ git ls-remote . refs/heads/master >expect &&
+ git ls-remote src refs/heads/master >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'push to update (protected, forced)' '
+ setup_srcdst_basic &&
+ (
+ cd dst &&
+ test_commit D &&
+ git push --force --force-with-lease=master:master origin master
+ ) &&
+ git ls-remote dst refs/heads/master >expect &&
+ git ls-remote src refs/heads/master >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'push to update (protected, tracking)' '
+ setup_srcdst_basic &&
+ (
+ cd src &&
+ git checkout master &&
+ test_commit D &&
+ git checkout HEAD^0
+ ) &&
+ git ls-remote src refs/heads/master >expect &&
+ (
+ cd dst &&
+ test_commit E &&
+ git ls-remote . refs/remotes/origin/master >expect &&
+ test_must_fail git push --force-with-lease=master origin master &&
+ git ls-remote . refs/remotes/origin/master >actual &&
+ test_cmp expect actual
+ ) &&
+ git ls-remote src refs/heads/master >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'push to update (protected, tracking, forced)' '
+ setup_srcdst_basic &&
+ (
+ cd src &&
+ git checkout master &&
+ test_commit D &&
+ git checkout HEAD^0
+ ) &&
+ (
+ cd dst &&
+ test_commit E &&
+ git ls-remote . refs/remotes/origin/master >expect &&
+ git push --force --force-with-lease=master origin master
+ ) &&
+ git ls-remote dst refs/heads/master >expect &&
+ git ls-remote src refs/heads/master >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'push to update (allowed)' '
+ setup_srcdst_basic &&
+ (
+ cd dst &&
+ test_commit D &&
+ git push --force-with-lease=master:master^ origin master
+ ) &&
+ git ls-remote dst refs/heads/master >expect &&
+ git ls-remote src refs/heads/master >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'push to update (allowed, tracking)' '
+ setup_srcdst_basic &&
+ (
+ cd dst &&
+ test_commit D &&
+ git push --force-with-lease=master origin master
+ ) &&
+ git ls-remote dst refs/heads/master >expect &&
+ git ls-remote src refs/heads/master >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'push to update (allowed even though no-ff)' '
+ setup_srcdst_basic &&
+ (
+ cd dst &&
+ git reset --hard HEAD^ &&
+ test_commit D &&
+ git push --force-with-lease=master origin master
+ ) &&
+ git ls-remote dst refs/heads/master >expect &&
+ git ls-remote src refs/heads/master >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'push to delete (protected)' '
+ setup_srcdst_basic &&
+ git ls-remote src refs/heads/master >expect &&
+ (
+ cd dst &&
+ test_must_fail git push --force-with-lease=master:master^ origin :master
+ ) &&
+ git ls-remote src refs/heads/master >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'push to delete (protected, forced)' '
+ setup_srcdst_basic &&
+ (
+ cd dst &&
+ git push --force --force-with-lease=master:master^ origin :master
+ ) &&
+ >expect &&
+ git ls-remote src refs/heads/master >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'push to delete (allowed)' '
+ setup_srcdst_basic &&
+ (
+ cd dst &&
+ git push --force-with-lease=master origin :master
+ ) &&
+ >expect &&
+ git ls-remote src refs/heads/master >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'cover everything with default force-with-lease (protected)' '
+ setup_srcdst_basic &&
+ (
+ cd src &&
+ git branch naster master^
+ )
+ git ls-remote src refs/heads/\* >expect &&
+ (
+ cd dst &&
+ test_must_fail git push --force-with-lease origin master master:naster
+ ) &&
+ git ls-remote src refs/heads/\* >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'cover everything with default force-with-lease (allowed)' '
+ setup_srcdst_basic &&
+ (
+ cd src &&
+ git branch naster master^
+ )
+ (
+ cd dst &&
+ git fetch &&
+ git push --force-with-lease origin master master:naster
+ ) &&
+ git ls-remote dst refs/heads/master |
+ sed -e "s/master/naster/" >expect &&
+ git ls-remote src refs/heads/naster >actual &&
+ test_cmp expect actual
+'
+
+test_done
'
test_http_push_nonff "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git \
- "$ROOT_PATH"/test_repo_clone master
+ "$ROOT_PATH"/test_repo_clone master success
test_expect_success 'push fails for non-fast-forward refs unmatched by remote helper' '
# create a dissimilarly-named remote ref so that git is unable to match the
test_cmp expect actual
'
+cat >cookies.txt <<EOF
+127.0.0.1 FALSE /smart_cookies/ FALSE 0 othername othervalue
+EOF
+cat >expect_cookies.txt <<EOF
+
+127.0.0.1 FALSE /smart_cookies/ FALSE 0 othername othervalue
+127.0.0.1 FALSE /smart_cookies/repo.git/info/ FALSE 0 name value
+EOF
+test_expect_success 'cookies stored in http.cookiefile when http.savecookies set' '
+ git config http.cookiefile cookies.txt &&
+ git config http.savecookies true &&
+ git ls-remote $HTTPD_URL/smart_cookies/repo.git master &&
+ tail -3 cookies.txt > cookies_tail.txt
+ test_cmp expect_cookies.txt cookies_tail.txt
+'
+
test -n "$GIT_TEST_LONG" && test_set_prereq EXPENSIVE
test_expect_success EXPENSIVE 'create 50,000 tags in the repo' '
+++ /dev/null
-#!/bin/sh
-#
-# Copyright (c) 2010 Sverre Rabbelier
-#
-
-test_description='Test python remote-helper framework'
-
-. ./test-lib.sh
-
-if ! test_have_prereq PYTHON ; then
- skip_all='skipping python remote-helper tests, python not available'
- test_done
-fi
-
-"$PYTHON_PATH" -c '
-import sys
-if sys.hexversion < 0x02040000:
- sys.exit(1)
-' || {
- skip_all='skipping python remote-helper tests, python version < 2.4'
- test_done
-}
-
-compare_refs() {
- git --git-dir="$1/.git" rev-parse --verify $2 >expect &&
- git --git-dir="$3/.git" rev-parse --verify $4 >actual &&
- test_cmp expect actual
-}
-
-test_expect_success 'setup repository' '
- git init --bare server/.git &&
- git clone server public &&
- (cd public &&
- echo content >file &&
- git add file &&
- git commit -m one &&
- git push origin master)
-'
-
-test_expect_success 'cloning from local repo' '
- git clone "testpy::${PWD}/server" localclone &&
- test_cmp public/file localclone/file
-'
-
-test_expect_success 'cloning from remote repo' '
- git clone "testpy::file://${PWD}/server" clone &&
- test_cmp public/file clone/file
-'
-
-test_expect_success 'create new commit on remote' '
- (cd public &&
- echo content >>file &&
- git commit -a -m two &&
- git push)
-'
-
-test_expect_success 'pulling from local repo' '
- (cd localclone && git pull) &&
- test_cmp public/file localclone/file
-'
-
-test_expect_success 'pulling from remote remote' '
- (cd clone && git pull) &&
- test_cmp public/file clone/file
-'
-
-test_expect_success 'pushing to local repo' '
- (cd localclone &&
- echo content >>file &&
- git commit -a -m three &&
- git push) &&
- compare_refs localclone HEAD server HEAD
-'
-
-# Generally, skip this test. It demonstrates a now-fixed race in
-# git-remote-testpy, but is too slow to leave in for general use.
-: test_expect_success 'racily pushing to local repo' '
- test_when_finished "rm -rf server2 localclone2" &&
- cp -R server server2 &&
- git clone "testpy::${PWD}/server2" localclone2 &&
- (cd localclone2 &&
- echo content >>file &&
- git commit -a -m three &&
- GIT_REMOTE_TESTGIT_SLEEPY=2 git push) &&
- compare_refs localclone2 HEAD server2 HEAD
-'
-
-test_expect_success 'synch with changes from localclone' '
- (cd clone &&
- git pull)
-'
-
-test_expect_success 'pushing remote local repo' '
- (cd clone &&
- echo content >>file &&
- git commit -a -m four &&
- git push) &&
- compare_refs clone HEAD server HEAD
-'
-
-test_expect_success 'fetch new branch' '
- (cd public &&
- git checkout -b new &&
- echo content >>file &&
- git commit -a -m five &&
- git push origin new
- ) &&
- (cd localclone &&
- git fetch origin new
- ) &&
- compare_refs public HEAD localclone FETCH_HEAD
-'
-
-test_expect_success 'fetch multiple branches' '
- (cd localclone &&
- git fetch
- ) &&
- compare_refs server master localclone refs/remotes/origin/master &&
- compare_refs server new localclone refs/remotes/origin/new
-'
-
-test_expect_success 'push when remote has extra refs' '
- (cd clone &&
- echo content >>file &&
- git commit -a -m six &&
- git push
- ) &&
- compare_refs clone master server master
-'
-
-test_expect_success 'push new branch by name' '
- (cd clone &&
- git checkout -b new-name &&
- echo content >>file &&
- git commit -a -m seven &&
- git push origin new-name
- ) &&
- compare_refs clone HEAD server refs/heads/new-name
-'
-
-test_expect_failure 'push new branch with old:new refspec' '
- (cd clone &&
- git push origin new-name:new-refspec
- ) &&
- compare_refs clone HEAD server refs/heads/new-refspec
-'
-
-test_expect_success 'proper failure checks for fetching' '
- (GIT_REMOTE_TESTGIT_FAILURE=1 &&
- export GIT_REMOTE_TESTGIT_FAILURE &&
- cd localclone &&
- test_must_fail git fetch 2>&1 | \
- grep "Error while running fast-import"
- )
-'
-
-# We sleep to give fast-export a chance to catch the SIGPIPE
-test_expect_failure 'proper failure checks for pushing' '
- (GIT_REMOTE_TESTGIT_FAILURE=1 &&
- export GIT_REMOTE_TESTGIT_FAILURE &&
- GIT_REMOTE_TESTGIT_SLEEPY=1 &&
- export GIT_REMOTE_TESTGIT_SLEEPY &&
- cd localclone &&
- test_must_fail git push --all 2>&1 | \
- grep "Error while running fast-export"
- )
-'
-
-test_done
)
'
+test_expect_success 'push update refs disabled by no-private-update' '
+ (cd local &&
+ echo more-update >>file &&
+ git commit -a -m more-update &&
+ git rev-parse --verify testgit/origin/heads/update >expect &&
+ GIT_REMOTE_TESTGIT_NO_PRIVATE_UPDATE=t git push origin update &&
+ git rev-parse --verify testgit/origin/heads/update >actual &&
+ test_cmp expect actual
+ )
+'
+
test_expect_success 'push update refs failure' '
(cd local &&
git checkout update &&
--- /dev/null
+#!/bin/sh
+
+test_description='ext::cmd remote "connect" helper'
+. ./test-lib.sh
+
+test_expect_success setup '
+ test_tick &&
+ git commit --allow-empty -m initial &&
+ test_tick &&
+ git commit --allow-empty -m second &&
+ test_tick &&
+ git commit --allow-empty -m third &&
+ test_tick &&
+ git tag -a -m "tip three" three &&
+
+ test_tick &&
+ git commit --allow-empty -m fourth
+'
+
+test_expect_success clone '
+ cmd=$(echo "echo >&2 ext::sh invoked && %S .." | sed -e "s/ /% /g") &&
+ git clone "ext::sh -c %S% ." dst &&
+ git for-each-ref refs/heads/ refs/tags/ >expect &&
+ (
+ cd dst &&
+ git config remote.origin.url "ext::sh -c $cmd" &&
+ git for-each-ref refs/heads/ refs/tags/
+ ) >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'update following tag' '
+ test_tick &&
+ git commit --allow-empty -m fifth &&
+ test_tick &&
+ git tag -a -m "tip five" five &&
+ git for-each-ref refs/heads/ refs/tags/ >expect &&
+ (
+ cd dst &&
+ git pull &&
+ git for-each-ref refs/heads/ refs/tags/ >../actual
+ ) &&
+ test_cmp expect actual
+'
+
+test_expect_success 'update backfilled tag' '
+ test_tick &&
+ git commit --allow-empty -m sixth &&
+ test_tick &&
+ git tag -a -m "tip two" two three^1 &&
+ git for-each-ref refs/heads/ refs/tags/ >expect &&
+ (
+ cd dst &&
+ git pull &&
+ git for-each-ref refs/heads/ refs/tags/ >../actual
+ ) &&
+ test_cmp expect actual
+'
+
+test_expect_success 'update backfilled tag without primary transfer' '
+ test_tick &&
+ git tag -a -m "tip one " one two^1 &&
+ git for-each-ref refs/heads/ refs/tags/ >expect &&
+ (
+ cd dst &&
+ git pull &&
+ git for-each-ref refs/heads/ refs/tags/ >../actual
+ ) &&
+ test_cmp expect actual
+'
+
+test_done
}
'
+test_expect_success '--full-diff is not affected by --parents' '
+ git log -p --pretty="%H" --full-diff -- file >expected &&
+ git log -p --pretty="%H" --full-diff --parents -- file >actual &&
+ test_cmp expected actual
+'
+
test_done
printf "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n" >sub/file &&
echo foo >dir/file-in-the-way &&
git add -A &&
- git commit -m "Common commmit" &&
+ git commit -m "Common commit" &&
echo 11 >>sub/file &&
echo more >>dir/file-in-the-way &&
mkdir one &&
echo stuff >one/file &&
git add -A &&
- git commit -m "Common commmit" &&
+ git commit -m "Common commit" &&
git mv one/file destdir &&
git commit -m "Renamed to destdir" &&
echo stuff >one/file &&
echo other >two/file &&
git add -A &&
- git commit -m "Common commmit" &&
+ git commit -m "Common commit" &&
git rm -rf one &&
git mv two/file one &&
echo stuff >original &&
git add -A &&
- git commit -m "Common commmit" &&
+ git commit -m "Common commit" &&
mkdir two &&
>two/file &&
mkdir one two &&
touch one/file two/file &&
git add -A &&
- git commit -m "Common commmit" &&
+ git commit -m "Common commit" &&
git rm -rf one &&
git mv original one &&
printf "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n" >original &&
git add -A &&
- git commit -m "Common commmit" &&
+ git commit -m "Common commit" &&
git mv original rename &&
echo 11 >>rename &&
mkdir df &&
printf "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n" >df/file &&
git add -A &&
- git commit -m "Common commmit" &&
+ git commit -m "Common commit" &&
git mv df/file temp &&
rm -rf df &&
git reset --hard HEAD^ &&
git checkout -b b4 origin &&
advance e &&
- advance f
+ advance f &&
+ git checkout -b brokenbase origin &&
+ git checkout -b b5 --track brokenbase &&
+ advance g &&
+ git branch -d brokenbase &&
+ git checkout -b b6 origin
) &&
git checkout -b follower --track master &&
- advance g
+ advance h
'
script='s/^..\(b.\)[ 0-9a-f]*\[\([^]]*\)\].*/\1 \2/p'
b2 origin/master: ahead 1, behind 1
b3 origin/master: behind 1
b4 origin/master: ahead 2
+b5 brokenbase: gone
+b6 origin/master
EOF
test_expect_success 'branch -vv' '
test_i18ncmp expect actual
'
-test_expect_success 'checkout' '
+test_expect_success 'checkout (diverged from upstream)' '
(
cd test && git checkout b1
) >actual &&
test_i18ngrep "is ahead of" actual
'
-test_expect_success 'status' '
+test_expect_success 'checkout (upstream is gone)' '
+ (
+ cd test &&
+ git checkout b5
+ ) >actual &&
+ test_i18ngrep "is based on .*, but the upstream is gone." actual
+'
+
+test_expect_success 'checkout (up-to-date with upstream)' '
+ (
+ cd test && git checkout b6
+ ) >actual &&
+ test_i18ngrep "Your branch is up-to-date with .origin/master" actual
+'
+
+test_expect_success 'status (diverged from upstream)' '
(
cd test &&
git checkout b1 >/dev/null &&
test_i18ngrep "have 1 and 1 different" actual
'
+test_expect_success 'status (upstream is gone)' '
+ (
+ cd test &&
+ git checkout b5 >/dev/null &&
+ # reports nothing to commit
+ test_must_fail git commit --dry-run
+ ) >actual &&
+ test_i18ngrep "is based on .*, but the upstream is gone." actual
+'
+
+test_expect_success 'status (up-to-date with upstream)' '
+ (
+ cd test &&
+ git checkout b6 >/dev/null &&
+ # reports nothing to commit
+ test_must_fail git commit --dry-run
+ ) >actual &&
+ test_i18ngrep "Your branch is up-to-date with .origin/master" actual
+'
+
+cat >expect <<\EOF
+## b1...origin/master [ahead 1, behind 1]
+EOF
+
+test_expect_success 'status -s -b (diverged from upstream)' '
+ (
+ cd test &&
+ git checkout b1 >/dev/null &&
+ git status -s -b | head -1
+ ) >actual &&
+ test_i18ncmp expect actual
+'
+
+cat >expect <<\EOF
+## b5...brokenbase [gone]
+EOF
+
+test_expect_success 'status -s -b (upstream is gone)' '
+ (
+ cd test &&
+ git checkout b5 >/dev/null &&
+ git status -s -b | head -1
+ ) >actual &&
+ test_i18ncmp expect actual
+'
+
+cat >expect <<\EOF
+## b6...origin/master
+EOF
+
+test_expect_success 'status -s -b (up-to-date with upstream)' '
+ (
+ cd test &&
+ git checkout b6 >/dev/null &&
+ git status -s -b | head -1
+ ) >actual &&
+ test_i18ncmp expect actual
+'
+
test_expect_success 'fail to track lightweight tags' '
git checkout master &&
git tag light &&
test_description='Test git rev-parse with different parent options'
. ./test-lib.sh
-. "$TEST_DIRECTORY"/lib-t6000.sh # t6xxx specific functions
-
-date >path0
-git update-index --add path0
-save_tag tree git write-tree
-hide_error save_tag start unique_commit "start" tree
-save_tag second unique_commit "second" tree -p start
-hide_error save_tag start2 unique_commit "start2" tree
-save_tag two_parents unique_commit "next" tree -p second -p start2
-save_tag final unique_commit "final" tree -p two_parents
-
-test_expect_success 'start is valid' 'git rev-parse start | grep "^[0-9a-f]\{40\}$"'
-test_expect_success 'start^0' "test $(cat .git/refs/tags/start) = $(git rev-parse start^0)"
-test_expect_success 'start^1 not valid' "if git rev-parse --verify start^1; then false; else :; fi"
-test_expect_success 'second^1 = second^' "test $(git rev-parse second^1) = $(git rev-parse second^)"
-test_expect_success 'final^1^1^1' "test $(git rev-parse start) = $(git rev-parse final^1^1^1)"
-test_expect_success 'final^1^1^1 = final^^^' "test $(git rev-parse final^1^1^1) = $(git rev-parse final^^^)"
-test_expect_success 'final^1^2' "test $(git rev-parse start2) = $(git rev-parse final^1^2)"
-test_expect_success 'final^1^2 != final^1^1' "test $(git rev-parse final^1^2) != $(git rev-parse final^1^1)"
-test_expect_success 'final^1^3 not valid' "if git rev-parse --verify final^1^3; then false; else :; fi"
-test_expect_success '--verify start2^1' 'test_must_fail git rev-parse --verify start2^1'
-test_expect_success '--verify start2^0' 'git rev-parse --verify start2^0'
-test_expect_success 'final^1^@ = final^1^1 final^1^2' "test \"$(git rev-parse final^1^@)\" = \"$(git rev-parse final^1^1 final^1^2)\""
-test_expect_success 'final^1^! = final^1 ^final^1^1 ^final^1^2' "test \"$(git rev-parse final^1^\!)\" = \"$(git rev-parse final^1 ^final^1^1 ^final^1^2)\""
-
-test_expect_success 'repack for next test' 'git repack -a -d'
+
+test_cmp_rev_output () {
+ git rev-parse --verify "$1" >expect &&
+ eval "$2" >actual &&
+ test_cmp expect actual
+}
+
+test_expect_success 'setup' '
+ test_commit start &&
+ test_commit second &&
+ git checkout --orphan tmp &&
+ test_commit start2 &&
+ git checkout master &&
+ git merge -m next start2 &&
+ test_commit final
+'
+
+test_expect_success 'start is valid' '
+ git rev-parse start | grep "^[0-9a-f]\{40\}$"
+'
+
+test_expect_success 'start^0' '
+ test_cmp_rev_output tags/start "git rev-parse start^0"
+'
+
+test_expect_success 'start^1 not valid' '
+ test_must_fail git rev-parse --verify start^1
+'
+
+test_expect_success 'second^1 = second^' '
+ test_cmp_rev_output second^ "git rev-parse second^1"
+'
+
+test_expect_success 'final^1^1^1' '
+ test_cmp_rev_output start "git rev-parse final^1^1^1"
+'
+
+test_expect_success 'final^1^1^1 = final^^^' '
+ test_cmp_rev_output final^^^ "git rev-parse final^1^1^1"
+'
+
+test_expect_success 'final^1^2' '
+ test_cmp_rev_output start2 "git rev-parse final^1^2"
+'
+
+test_expect_success 'final^1^2 != final^1^1' '
+ test $(git rev-parse final^1^2) != $(git rev-parse final^1^1)
+'
+
+test_expect_success 'final^1^3 not valid' '
+ test_must_fail git rev-parse --verify final^1^3
+'
+
+test_expect_success '--verify start2^1' '
+ test_must_fail git rev-parse --verify start2^1
+'
+
+test_expect_success '--verify start2^0' '
+ git rev-parse --verify start2^0
+'
+
+test_expect_success 'final^1^@ = final^1^1 final^1^2' '
+ git rev-parse final^1^1 final^1^2 >expect &&
+ git rev-parse final^1^@ >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'final^1^! = final^1 ^final^1^1 ^final^1^2' '
+ git rev-parse final^1 ^final^1^1 ^final^1^2 >expect &&
+ git rev-parse final^1^! >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'repack for next test' '
+ git repack -a -d
+'
+
test_expect_success 'short SHA-1 works' '
- start=`git rev-parse --verify start` &&
- echo $start &&
- abbrv=`echo $start | sed s/.\$//` &&
- echo $abbrv &&
- abbrv=`git rev-parse --verify $abbrv` &&
- echo $abbrv &&
- test $start = $abbrv'
+ start=$(git rev-parse --verify start) &&
+ test_cmp_rev_output start "git rev-parse ${start%?}"
+'
test_done
test_cmp expect actual
'
+test_expect_success 'star pathspec globs' '
+ cat >expect <<-\EOF &&
+ bracket
+ star
+ vanilla
+ EOF
+ git log --format=%s -- ":(glob)f*" >actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'bracket pathspec globs and matches literal brackets' '
cat >expect <<-\EOF &&
bracket
test_cmp expect actual
'
+test_expect_success 'bracket pathspec globs and matches literal brackets' '
+ cat >expect <<-\EOF &&
+ bracket
+ vanilla
+ EOF
+ git log --format=%s -- ":(glob)f[o][o]" >actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'no-glob option matches literally (vanilla)' '
echo vanilla >expect &&
git --literal-pathspecs log --format=%s -- foo >actual &&
test_cmp expect actual
'
+test_expect_success 'no-glob option matches literally (vanilla)' '
+ echo vanilla >expect &&
+ git log --format=%s -- ":(literal)foo" >actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'no-glob option matches literally (star)' '
echo star >expect &&
git --literal-pathspecs log --format=%s -- "f*" >actual &&
test_cmp expect actual
'
+test_expect_success 'no-glob option matches literally (star)' '
+ echo star >expect &&
+ git log --format=%s -- ":(literal)f*" >actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'no-glob option matches literally (bracket)' '
echo bracket >expect &&
git --literal-pathspecs log --format=%s -- "f[o][o]" >actual &&
test_cmp expect actual
'
+test_expect_success 'no-glob option matches literally (bracket)' '
+ echo bracket >expect &&
+ git log --format=%s -- ":(literal)f[o][o]" >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'no-glob option disables :(literal)' '
+ : >expect &&
+ git --literal-pathspecs log --format=%s -- ":(literal)foo" >actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'no-glob environment variable works' '
echo star >expect &&
GIT_LITERAL_PATHSPECS=1 git log --format=%s -- "f*" >actual &&
test_cmp expect actual
'
+test_expect_success 'setup xxx/bar' '
+ mkdir xxx &&
+ test_commit xxx xxx/bar
+'
+
+test_expect_success '**/ works with :(glob)' '
+ cat >expect <<-\EOF &&
+ xxx
+ unrelated
+ EOF
+ git log --format=%s -- ":(glob)**/bar" >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '**/ does not work with --noglob-pathspecs' '
+ : >expect &&
+ git --noglob-pathspecs log --format=%s -- "**/bar" >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '**/ works with :(glob) and --noglob-pathspecs' '
+ cat >expect <<-\EOF &&
+ xxx
+ unrelated
+ EOF
+ git --noglob-pathspecs log --format=%s -- ":(glob)**/bar" >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '**/ works with --glob-pathspecs' '
+ cat >expect <<-\EOF &&
+ xxx
+ unrelated
+ EOF
+ git --glob-pathspecs log --format=%s -- "**/bar" >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '**/ does not work with :(literal) and --glob-pathspecs' '
+ : >expect &&
+ git --glob-pathspecs log --format=%s -- ":(literal)**/bar" >actual &&
+ test_cmp expect actual
+'
+
test_done
--- /dev/null
+#!/bin/sh
+
+test_description='test case insensitive pathspec limiting'
+. ./test-lib.sh
+
+if test_have_prereq CASE_INSENSITIVE_FS
+then
+ skip_all='skipping case sensitive tests - case insensitive file system'
+ test_done
+fi
+
+test_expect_success 'create commits with glob characters' '
+ test_commit bar bar &&
+ test_commit bAr bAr &&
+ test_commit BAR BAR &&
+ mkdir foo &&
+ test_commit foo/bar foo/bar &&
+ test_commit foo/bAr foo/bAr &&
+ test_commit foo/BAR foo/BAR &&
+ mkdir fOo &&
+ test_commit fOo/bar fOo/bar &&
+ test_commit fOo/bAr fOo/bAr &&
+ test_commit fOo/BAR fOo/BAR &&
+ mkdir FOO &&
+ test_commit FOO/bar FOO/bar &&
+ test_commit FOO/bAr FOO/bAr &&
+ test_commit FOO/BAR FOO/BAR
+'
+
+test_expect_success 'tree_entry_interesting matches bar' '
+ echo bar >expect &&
+ git log --format=%s -- "bar" >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'tree_entry_interesting matches :(icase)bar' '
+ cat <<-EOF >expect &&
+ BAR
+ bAr
+ bar
+ EOF
+ git log --format=%s -- ":(icase)bar" >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'tree_entry_interesting matches :(icase)bar with prefix' '
+ cat <<-EOF >expect &&
+ fOo/BAR
+ fOo/bAr
+ fOo/bar
+ EOF
+ ( cd fOo && git log --format=%s -- ":(icase)bar" ) >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'tree_entry_interesting matches :(icase)bar with empty prefix' '
+ cat <<-EOF >expect &&
+ FOO/BAR
+ FOO/bAr
+ FOO/bar
+ fOo/BAR
+ fOo/bAr
+ fOo/bar
+ foo/BAR
+ foo/bAr
+ foo/bar
+ EOF
+ ( cd fOo && git log --format=%s -- ":(icase)../foo/bar" ) >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'match_pathspec_depth matches :(icase)bar' '
+ cat <<-EOF >expect &&
+ BAR
+ bAr
+ bar
+ EOF
+ git ls-files ":(icase)bar" >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'match_pathspec_depth matches :(icase)bar with prefix' '
+ cat <<-EOF >expect &&
+ fOo/BAR
+ fOo/bAr
+ fOo/bar
+ EOF
+ ( cd fOo && git ls-files --full-name ":(icase)bar" ) >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'match_pathspec_depth matches :(icase)bar with empty prefix' '
+ cat <<-EOF >expect &&
+ bar
+ fOo/BAR
+ fOo/bAr
+ fOo/bar
+ EOF
+ ( cd fOo && git ls-files --full-name ":(icase)bar" ../bar ) >actual &&
+ test_cmp expect actual
+'
+
+test_done
rm -f moved symlink
+test_expect_success 'setup submodule' '
+ git commit -m initial &&
+ git reset --hard &&
+ git submodule add ./. sub &&
+ echo content >file &&
+ git add file &&
+ git commit -m "added sub and file"
+'
+
+test_expect_success 'git mv cannot move a submodule in a file' '
+ test_must_fail git mv sub file
+'
+
+test_expect_success 'git mv moves a submodule with a .git directory and no .gitmodules' '
+ entry="$(git ls-files --stage sub | cut -f 1)" &&
+ git rm .gitmodules &&
+ (
+ cd sub &&
+ rm -f .git &&
+ cp -a ../.git/modules/sub .git &&
+ GIT_WORK_TREE=. git config --unset core.worktree
+ ) &&
+ mkdir mod &&
+ git mv sub mod/sub &&
+ ! test -e sub &&
+ [ "$entry" = "$(git ls-files --stage mod/sub | cut -f 1)" ] &&
+ (
+ cd mod/sub &&
+ git status
+ ) &&
+ git update-index --refresh &&
+ git diff-files --quiet
+'
+
+test_expect_success 'git mv moves a submodule with gitfile' '
+ rm -rf mod/sub &&
+ git reset --hard &&
+ git submodule update &&
+ entry="$(git ls-files --stage sub | cut -f 1)" &&
+ (
+ cd mod &&
+ git mv ../sub/ .
+ ) &&
+ ! test -e sub &&
+ [ "$entry" = "$(git ls-files --stage mod/sub | cut -f 1)" ] &&
+ (
+ cd mod/sub &&
+ git status
+ ) &&
+ echo mod/sub >expected &&
+ git config -f .gitmodules submodule.sub.path >actual &&
+ test_cmp expected actual &&
+ git update-index --refresh &&
+ git diff-files --quiet
+'
+
+test_expect_success 'mv does not complain when no .gitmodules file is found' '
+ rm -rf mod/sub &&
+ git reset --hard &&
+ git submodule update &&
+ git rm .gitmodules &&
+ entry="$(git ls-files --stage sub | cut -f 1)" &&
+ git mv sub mod/sub 2>actual.err &&
+ ! test -s actual.err &&
+ ! test -e sub &&
+ [ "$entry" = "$(git ls-files --stage mod/sub | cut -f 1)" ] &&
+ (
+ cd mod/sub &&
+ git status
+ ) &&
+ git update-index --refresh &&
+ git diff-files --quiet
+'
+
+test_expect_success 'mv will error out on a modified .gitmodules file unless staged' '
+ rm -rf mod/sub &&
+ git reset --hard &&
+ git submodule update &&
+ git config -f .gitmodules foo.bar true &&
+ entry="$(git ls-files --stage sub | cut -f 1)" &&
+ test_must_fail git mv sub mod/sub 2>actual.err &&
+ test -s actual.err &&
+ test -e sub &&
+ git diff-files --quiet -- sub &&
+ git add .gitmodules &&
+ git mv sub mod/sub 2>actual.err &&
+ ! test -s actual.err &&
+ ! test -e sub &&
+ [ "$entry" = "$(git ls-files --stage mod/sub | cut -f 1)" ] &&
+ (
+ cd mod/sub &&
+ git status
+ ) &&
+ git update-index --refresh &&
+ git diff-files --quiet
+'
+
+test_expect_success 'mv issues a warning when section is not found in .gitmodules' '
+ rm -rf mod/sub &&
+ git reset --hard &&
+ git submodule update &&
+ git config -f .gitmodules --remove-section submodule.sub &&
+ git add .gitmodules &&
+ entry="$(git ls-files --stage sub | cut -f 1)" &&
+ echo "warning: Could not find section in .gitmodules where path=sub" >expect.err &&
+ git mv sub mod/sub 2>actual.err &&
+ test_i18ncmp expect.err actual.err &&
+ ! test -e sub &&
+ [ "$entry" = "$(git ls-files --stage mod/sub | cut -f 1)" ] &&
+ (
+ cd mod/sub &&
+ git status
+ ) &&
+ git update-index --refresh &&
+ git diff-files --quiet
+'
+
+test_expect_success 'mv --dry-run does not touch the submodule or .gitmodules' '
+ rm -rf mod/sub &&
+ git reset --hard &&
+ git submodule update &&
+ git mv -n sub mod/sub 2>actual.err &&
+ test -f sub/.git &&
+ git diff-index --exit-code HEAD &&
+ git update-index --refresh &&
+ git diff-files --quiet -- sub .gitmodules
+'
+
test_done
--- /dev/null
+#!/bin/sh
+
+test_description='filter-branch removal of trees with null sha1'
+. ./test-lib.sh
+
+test_expect_success 'setup: base commits' '
+ test_commit one &&
+ test_commit two &&
+ test_commit three
+'
+
+test_expect_success 'setup: a commit with a bogus null sha1 in the tree' '
+ {
+ git ls-tree HEAD &&
+ printf "160000 commit $_z40\\tbroken\\n"
+ } >broken-tree
+ echo "add broken entry" >msg &&
+
+ tree=$(git mktree <broken-tree) &&
+ test_tick &&
+ commit=$(git commit-tree $tree -p HEAD <msg) &&
+ git update-ref HEAD "$commit"
+'
+
+# we have to make one more commit on top removing the broken
+# entry, since otherwise our index does not match HEAD (and filter-branch will
+# complain). We could make the index match HEAD, but doing so would involve
+# writing a null sha1 into the index.
+test_expect_success 'setup: bring HEAD and index in sync' '
+ test_tick &&
+ git commit -a -m "back to normal"
+'
+
+test_expect_success 'filter commands are still checked' '
+ test_must_fail git filter-branch \
+ --force --prune-empty \
+ --index-filter "git rm --cached --ignore-unmatch three.t"
+'
+
+test_expect_success 'removing the broken entry works' '
+ echo three >expect &&
+ git filter-branch \
+ --force --prune-empty \
+ --index-filter "git rm --cached --ignore-unmatch broken" &&
+ git log -1 --format=%s >actual &&
+ test_cmp expect actual
+'
+
+test_done
test_cmp expect actual
'
-cat >expect <<EOF
-# On branch side
-# You have unmerged paths.
-# (fix conflicts and run "git commit")
-#
-# Unmerged paths:
-# (use "git add/rm <file>..." as appropriate to mark resolution)
-#
-# deleted by us: foo
-#
+test_expect_success 'M/D conflict does not segfault' '
+ cat >expect <<EOF &&
+On branch side
+You have unmerged paths.
+ (fix conflicts and run "git commit")
+
+Unmerged paths:
+ (use "git add/rm <file>..." as appropriate to mark resolution)
+
+ deleted by us: foo
+
no changes added to commit (use "git add" and/or "git commit -a")
EOF
-
-test_expect_success 'M/D conflict does not segfault' '
mkdir mdconflict &&
(
cd mdconflict &&
test_commit on_second main.txt on_second &&
test_commit master conflict.txt master &&
test_must_fail git merge second_branch &&
- cat >expected <<-\EOF &&
- # On branch master
- # You have unmerged paths.
- # (fix conflicts and run "git commit")
- #
- # Unmerged paths:
- # (use "git add/rm <file>..." as appropriate to mark resolution)
- #
- # both added: conflict.txt
- # deleted by them: main.txt
- #
- no changes added to commit (use "git add" and/or "git commit -a")
- EOF
+ cat >expected <<\EOF &&
+On branch master
+You have unmerged paths.
+ (fix conflicts and run "git commit")
+
+Unmerged paths:
+ (use "git add/rm <file>..." as appropriate to mark resolution)
+
+ both added: conflict.txt
+ deleted by them: main.txt
+
+no changes added to commit (use "git add" and/or "git commit -a")
+EOF
git status --untracked-files=no >actual &&
test_i18ncmp expected actual
'
test_expect_success 'status when conflicts with add and rm advice (both deleted)' '
test_must_fail git merge conflict &&
- cat >expected <<-\EOF &&
- # On branch conflict_second
- # You have unmerged paths.
- # (fix conflicts and run "git commit")
- #
- # Unmerged paths:
- # (use "git add/rm <file>..." as appropriate to mark resolution)
- #
- # both deleted: main.txt
- # added by them: sub_master.txt
- # added by us: sub_second.txt
- #
- no changes added to commit (use "git add" and/or "git commit -a")
- EOF
+ cat >expected <<\EOF &&
+On branch conflict_second
+You have unmerged paths.
+ (fix conflicts and run "git commit")
+
+Unmerged paths:
+ (use "git add/rm <file>..." as appropriate to mark resolution)
+
+ both deleted: main.txt
+ added by them: sub_master.txt
+ added by us: sub_second.txt
+
+no changes added to commit (use "git add" and/or "git commit -a")
+EOF
git status --untracked-files=no >actual &&
test_i18ncmp expected actual
'
test_must_fail git merge conflict &&
git add sub_master.txt &&
git add sub_second.txt &&
- cat >expected <<-\EOF &&
- # On branch conflict_second
- # You have unmerged paths.
- # (fix conflicts and run "git commit")
- #
- # Changes to be committed:
- #
- # new file: sub_master.txt
- #
- # Unmerged paths:
- # (use "git rm <file>..." to mark resolution)
- #
- # both deleted: main.txt
- #
- # Untracked files not listed (use -u option to show untracked files)
- EOF
+ cat >expected <<\EOF &&
+On branch conflict_second
+You have unmerged paths.
+ (fix conflicts and run "git commit")
+
+Changes to be committed:
+
+ new file: sub_master.txt
+
+Unmerged paths:
+ (use "git rm <file>..." to mark resolution)
+
+ both deleted: main.txt
+
+Untracked files not listed (use -u option to show untracked files)
+EOF
git status --untracked-files=no >actual &&
test_i18ncmp expected actual &&
git reset --hard &&
test_expect_success 'reset' '
git add a b &&
git reset &&
- test "$(git ls-files)" = ""
+
+ >expect &&
+ git ls-files >actual &&
+ test_cmp expect actual
'
test_expect_success 'reset HEAD' '
rm .git/index &&
git add a b &&
git reset a &&
- test "$(git ls-files)" = "b"
+
+ echo b >expect &&
+ git ls-files >actual &&
+ test_cmp expect actual
'
-test_expect_success 'reset -p' '
+test_expect_success PERL 'reset -p' '
rm .git/index &&
git add a &&
- echo y | git reset -p &&
- test "$(git ls-files)" = ""
+ echo y >yes &&
+ git reset -p <yes &&
+
+ >expect &&
+ git ls-files >actual &&
+ test_cmp expect actual
'
test_expect_success 'reset --soft is a no-op' '
rm .git/index &&
git add a &&
- git reset --soft
- test "$(git ls-files)" = "a"
+ git reset --soft &&
+
+ echo a >expect &&
+ git ls-files >actual &&
+ test_cmp expect actual
'
test_expect_success 'reset --hard' '
rm .git/index &&
git add a &&
+ test_when_finished "echo a >a" &&
git reset --hard &&
- test "$(git ls-files)" = "" &&
+
+ >expect &&
+ git ls-files >actual &&
+ test_cmp expect actual &&
test_path_is_missing a
'
test_cmp expect .git
) &&
echo "repo" >expect &&
- git config -f .gitmodules submodule.repo.path >actual &&
- test_cmp expect actual &&
+ test_must_fail git config -f .gitmodules submodule.repo.path &&
git config -f .gitmodules submodule.repo_new.path >actual &&
test_cmp expect actual&&
echo "$submodurl/repo" >expect &&
- git config -f .gitmodules submodule.repo.url >actual &&
- test_cmp expect actual &&
+ test_must_fail git config -f .gitmodules submodule.repo.url &&
echo "$submodurl/bare.git" >expect &&
git config -f .gitmodules submodule.repo_new.url >actual &&
test_cmp expect actual &&
git rm repo &&
test_must_fail git submodule add -q --name repo_new "$submodurl/repo.git" repo &&
test ! -d repo &&
- echo "repo" >expect &&
- git config -f .gitmodules submodule.repo_new.path >actual &&
- test_cmp expect actual&&
- echo "$submodurl/bare.git" >expect &&
- git config -f .gitmodules submodule.repo_new.url >actual &&
- test_cmp expect actual &&
+ test_must_fail git config -f .gitmodules submodule.repo_new.path &&
+ test_must_fail git config -f .gitmodules submodule.repo_new.url &&
echo "$submodurl/bare.git" >expect &&
git config submodule.repo_new.url >actual &&
test_cmp expect actual &&
git add sub &&
git commit -m "init sub"
) &&
- test_config core.precomposeunicode true &&
git submodule add ./"$svname" &&
git submodule >&2 &&
test -n "$(git submodule | grep "$svname")"
test_expect_success '--for-status' "
git submodule summary --for-status HEAD^ >actual &&
test_i18ncmp actual - <<EOF
-# Submodule changes to be committed:
-#
-# * sm1 $head6...0000000:
-#
-# * sm2 0000000...$head7 (2):
-# > Add foo9
-#
+* sm1 $head6...0000000:
+
+* sm2 0000000...$head7 (2):
+ > Add foo9
+
EOF
"
git submodule add ../merging merging &&
test_tick &&
git commit -m "rebasing"
- )
+ ) &&
(cd super &&
git submodule add ../none none &&
test_tick &&
'
test_expect_success 'using invalid commit with -C' '
- test_must_fail git commit -C bogus
+ test_must_fail git commit --allow-empty -C bogus
'
test_expect_success 'nothing to commit' '
test_i18ngrep "use \"git rm --cached <file>\.\.\.\" to unstage" output
'
+strip_comments () {
+ tab=' '
+ sed "s/^\# //; s/^\#$//; s/^#$tab/$tab/" <"$1" >"$1".tmp &&
+ rm "$1" && mv "$1".tmp "$1"
+}
+
test_expect_success 'status --column' '
- COLUMNS=50 git status --column="column dense" >output &&
cat >expect <<\EOF &&
# On branch master
# Changes to be committed:
# Untracked files:
# (use "git add <file>..." to include in what will be committed)
#
-# dir1/untracked dir2/untracked untracked
-# dir2/modified output
+# dir1/untracked dir2/untracked output
+# dir2/modified expect untracked
+#
EOF
+ COLUMNS=50 git -c status.displayCommentPrefix=true status --column="column dense" >output &&
+ test_i18ncmp expect output
+'
+
+test_expect_success 'status --column status.displayCommentPrefix=false' '
+ strip_comments expect &&
+ COLUMNS=49 git -c status.displayCommentPrefix=false status --column="column dense" >output &&
test_i18ncmp expect output
'
# expect
# output
# untracked
+#
EOF
-test_expect_success 'status (2)' '
- git status >output &&
+test_expect_success 'status with status.displayCommentPrefix=true' '
+ git -c status.displayCommentPrefix=true status >output &&
+ test_i18ncmp expect output
+'
+
+test_expect_success 'status with status.displayCommentPrefix=false' '
+ strip_comments expect &&
+ git -c status.displayCommentPrefix=false status >output &&
test_i18ncmp expect output
'
+test_expect_success 'setup fake editor' '
+ cat >.git/editor <<-\EOF &&
+ #! /bin/sh
+ cp "$1" output
+EOF
+ chmod 755 .git/editor
+'
+
+commit_template_commented () {
+ (
+ EDITOR=.git/editor &&
+ export EDITOR &&
+ # Fails due to empty message
+ test_must_fail git commit
+ ) &&
+ ! grep '^[^#]' output
+}
+
+test_expect_success 'commit ignores status.displayCommentPrefix=false in COMMIT_EDITMSG' '
+ commit_template_commented
+'
+
cat >expect <<\EOF
-# On branch master
-# Changes to be committed:
-# new file: dir2/added
-#
-# Changes not staged for commit:
-# modified: dir1/modified
-#
-# Untracked files:
-# dir1/untracked
-# dir2/modified
-# dir2/untracked
-# expect
-# output
-# untracked
+On branch master
+Changes to be committed:
+ new file: dir2/added
+
+Changes not staged for commit:
+ modified: dir1/modified
+
+Untracked files:
+ dir1/untracked
+ dir2/modified
+ dir2/untracked
+ expect
+ output
+ untracked
+
EOF
test_expect_success 'status (advice.statusHints false)' '
git status -s --ignored >output &&
test_cmp expect output &&
- cat >expect <<-\EOF &&
- # On branch master
- # Changes to be committed:
- # (use "git reset HEAD <file>..." to unstage)
- #
- # new file: dir2/added
- #
- # Changes not staged for commit:
- # (use "git add <file>..." to update what will be committed)
- # (use "git checkout -- <file>..." to discard changes in working directory)
- #
- # modified: dir1/modified
- #
- # Untracked files:
- # (use "git add <file>..." to include in what will be committed)
- #
- # dir2/modified
- # Ignored files:
- # (use "git add -f <file>..." to include in what will be committed)
- #
- # .gitignore
- # dir1/untracked
- # dir2/untracked
- # expect
- # output
- # untracked
- EOF
+ cat >expect <<\EOF &&
+On branch master
+Changes to be committed:
+ (use "git reset HEAD <file>..." to unstage)
+
+ new file: dir2/added
+
+Changes not staged for commit:
+ (use "git add <file>..." to update what will be committed)
+ (use "git checkout -- <file>..." to discard changes in working directory)
+
+ modified: dir1/modified
+
+Untracked files:
+ (use "git add <file>..." to include in what will be committed)
+
+ dir2/modified
+
+Ignored files:
+ (use "git add -f <file>..." to include in what will be committed)
+
+ .gitignore
+ dir1/untracked
+ dir2/untracked
+ expect
+ output
+ untracked
+
+EOF
git status --ignored >output &&
test_i18ncmp expect output
'
git status -s --ignored >output &&
test_cmp expect output &&
- cat >expect <<-\EOF &&
- # On branch master
- # Changes to be committed:
- # (use "git reset HEAD <file>..." to unstage)
- #
- # new file: dir2/added
- #
- # Changes not staged for commit:
- # (use "git add <file>..." to update what will be committed)
- # (use "git checkout -- <file>..." to discard changes in working directory)
- #
- # modified: dir1/modified
- #
- # Ignored files:
- # (use "git add -f <file>..." to include in what will be committed)
- #
- # .gitignore
- # dir1/untracked
- # dir2/modified
- # dir2/untracked
- # expect
- # output
- # untracked
- EOF
+ cat >expect <<\EOF &&
+On branch master
+Changes to be committed:
+ (use "git reset HEAD <file>..." to unstage)
+
+ new file: dir2/added
+
+Changes not staged for commit:
+ (use "git add <file>..." to update what will be committed)
+ (use "git checkout -- <file>..." to discard changes in working directory)
+
+ modified: dir1/modified
+
+Ignored files:
+ (use "git add -f <file>..." to include in what will be committed)
+
+ .gitignore
+ dir1/untracked
+ dir2/modified
+ dir2/untracked
+ expect
+ output
+ untracked
+
+EOF
git status --ignored >output &&
test_i18ncmp expect output
'
: >dir3/untracked2
'
-cat >expect <<EOF
-# On branch master
-# Changes to be committed:
-# (use "git reset HEAD <file>..." to unstage)
-#
-# new file: dir2/added
-#
-# Changes not staged for commit:
-# (use "git add <file>..." to update what will be committed)
-# (use "git checkout -- <file>..." to discard changes in working directory)
-#
-# modified: dir1/modified
-#
-# Untracked files not listed (use -u option to show untracked files)
-EOF
test_expect_success 'status -uno' '
+ cat >expect <<EOF &&
+On branch master
+Changes to be committed:
+ (use "git reset HEAD <file>..." to unstage)
+
+ new file: dir2/added
+
+Changes not staged for commit:
+ (use "git add <file>..." to update what will be committed)
+ (use "git checkout -- <file>..." to discard changes in working directory)
+
+ modified: dir1/modified
+
+Untracked files not listed (use -u option to show untracked files)
+EOF
git status -uno >output &&
test_i18ncmp expect output
'
test_i18ncmp expect output
'
-cat >expect <<EOF
-# On branch master
-# Changes to be committed:
-# new file: dir2/added
-#
-# Changes not staged for commit:
-# modified: dir1/modified
-#
-# Untracked files not listed
-EOF
test_expect_success 'status -uno (advice.statusHints false)' '
+ cat >expect <<EOF &&
+On branch master
+Changes to be committed:
+ new file: dir2/added
+
+Changes not staged for commit:
+ modified: dir1/modified
+
+Untracked files not listed
+EOF
test_config advice.statusHints false &&
git status -uno >output &&
test_i18ncmp expect output
test_cmp expect output
'
-cat >expect <<EOF
-# On branch master
-# Changes to be committed:
-# (use "git reset HEAD <file>..." to unstage)
-#
-# new file: dir2/added
-#
-# Changes not staged for commit:
-# (use "git add <file>..." to update what will be committed)
-# (use "git checkout -- <file>..." to discard changes in working directory)
-#
-# modified: dir1/modified
-#
-# Untracked files:
-# (use "git add <file>..." to include in what will be committed)
-#
-# dir1/untracked
-# dir2/modified
-# dir2/untracked
-# dir3/
-# expect
-# output
-# untracked
-EOF
test_expect_success 'status -unormal' '
+ cat >expect <<EOF &&
+On branch master
+Changes to be committed:
+ (use "git reset HEAD <file>..." to unstage)
+
+ new file: dir2/added
+
+Changes not staged for commit:
+ (use "git add <file>..." to update what will be committed)
+ (use "git checkout -- <file>..." to discard changes in working directory)
+
+ modified: dir1/modified
+
+Untracked files:
+ (use "git add <file>..." to include in what will be committed)
+
+ dir1/untracked
+ dir2/modified
+ dir2/untracked
+ dir3/
+ expect
+ output
+ untracked
+
+EOF
git status -unormal >output &&
test_i18ncmp expect output
'
test_cmp expect output
'
-cat >expect <<EOF
-# On branch master
-# Changes to be committed:
-# (use "git reset HEAD <file>..." to unstage)
-#
-# new file: dir2/added
-#
-# Changes not staged for commit:
-# (use "git add <file>..." to update what will be committed)
-# (use "git checkout -- <file>..." to discard changes in working directory)
-#
-# modified: dir1/modified
-#
-# Untracked files:
-# (use "git add <file>..." to include in what will be committed)
-#
-# dir1/untracked
-# dir2/modified
-# dir2/untracked
-# dir3/untracked1
-# dir3/untracked2
-# expect
-# output
-# untracked
-EOF
test_expect_success 'status -uall' '
+ cat >expect <<EOF &&
+On branch master
+Changes to be committed:
+ (use "git reset HEAD <file>..." to unstage)
+
+ new file: dir2/added
+
+Changes not staged for commit:
+ (use "git add <file>..." to update what will be committed)
+ (use "git checkout -- <file>..." to discard changes in working directory)
+
+ modified: dir1/modified
+
+Untracked files:
+ (use "git add <file>..." to include in what will be committed)
+
+ dir1/untracked
+ dir2/modified
+ dir2/untracked
+ dir3/untracked1
+ dir3/untracked2
+ expect
+ output
+ untracked
+
+EOF
git status -uall >output &&
test_i18ncmp expect output
'
test_cmp expect output
'
-cat >expect <<\EOF
-# On branch master
-# Changes to be committed:
-# (use "git reset HEAD <file>..." to unstage)
-#
-# new file: ../dir2/added
-#
-# Changes not staged for commit:
-# (use "git add <file>..." to update what will be committed)
-# (use "git checkout -- <file>..." to discard changes in working directory)
-#
-# modified: modified
-#
-# Untracked files:
-# (use "git add <file>..." to include in what will be committed)
-#
-# untracked
-# ../dir2/modified
-# ../dir2/untracked
-# ../expect
-# ../output
-# ../untracked
-EOF
-
test_expect_success 'status with relative paths' '
+ cat >expect <<\EOF &&
+On branch master
+Changes to be committed:
+ (use "git reset HEAD <file>..." to unstage)
+
+ new file: ../dir2/added
+
+Changes not staged for commit:
+ (use "git add <file>..." to update what will be committed)
+ (use "git checkout -- <file>..." to discard changes in working directory)
+
+ modified: modified
+
+Untracked files:
+ (use "git add <file>..." to include in what will be committed)
+
+ untracked
+ ../dir2/modified
+ ../dir2/untracked
+ ../expect
+ ../output
+ ../untracked
+
+EOF
(cd dir1 && git status) >output &&
test_i18ncmp expect output
'
'
-cat >expect <<\EOF
-# On branch <GREEN>master<RESET>
-# Changes to be committed:
-# (use "git reset HEAD <file>..." to unstage)
-#
-# <GREEN>new file: dir2/added<RESET>
-#
-# Changes not staged for commit:
-# (use "git add <file>..." to update what will be committed)
-# (use "git checkout -- <file>..." to discard changes in working directory)
-#
-# <RED>modified: dir1/modified<RESET>
-#
-# Untracked files:
-# (use "git add <file>..." to include in what will be committed)
-#
-# <BLUE>dir1/untracked<RESET>
-# <BLUE>dir2/modified<RESET>
-# <BLUE>dir2/untracked<RESET>
-# <BLUE>expect<RESET>
-# <BLUE>output<RESET>
-# <BLUE>untracked<RESET>
-EOF
-
test_expect_success 'status with color.ui' '
+ cat >expect <<\EOF &&
+On branch <GREEN>master<RESET>
+Changes to be committed:
+ (use "git reset HEAD <file>..." to unstage)
+
+ <GREEN>new file: dir2/added<RESET>
+
+Changes not staged for commit:
+ (use "git add <file>..." to update what will be committed)
+ (use "git checkout -- <file>..." to discard changes in working directory)
+
+ <RED>modified: dir1/modified<RESET>
+
+Untracked files:
+ (use "git add <file>..." to include in what will be committed)
+
+ <BLUE>dir1/untracked<RESET>
+ <BLUE>dir2/modified<RESET>
+ <BLUE>dir2/untracked<RESET>
+ <BLUE>expect<RESET>
+ <BLUE>output<RESET>
+ <BLUE>untracked<RESET>
+
+EOF
test_config color.ui always &&
git status | test_decode_color >output &&
test_i18ncmp expect output
'
-cat >expect <<\EOF
-# On branch master
-# Changes to be committed:
-# (use "git reset HEAD <file>..." to unstage)
-#
-# new file: dir2/added
-#
-# Changes not staged for commit:
-# (use "git add <file>..." to update what will be committed)
-# (use "git checkout -- <file>..." to discard changes in working directory)
-#
-# modified: dir1/modified
-#
-# Untracked files:
-# (use "git add <file>..." to include in what will be committed)
-#
-# dir1/untracked
-# dir2/modified
-# dir2/untracked
-# expect
-# output
-# untracked
-EOF
test_expect_success 'status without relative paths' '
+ cat >expect <<\EOF &&
+On branch master
+Changes to be committed:
+ (use "git reset HEAD <file>..." to unstage)
+
+ new file: dir2/added
+Changes not staged for commit:
+ (use "git add <file>..." to update what will be committed)
+ (use "git checkout -- <file>..." to discard changes in working directory)
+
+ modified: dir1/modified
+
+Untracked files:
+ (use "git add <file>..." to include in what will be committed)
+
+ dir1/untracked
+ dir2/modified
+ dir2/untracked
+ expect
+ output
+ untracked
+
+EOF
test_config status.relativePaths false &&
(cd dir1 && git status) >output &&
test_i18ncmp expect output
'
-cat <<EOF >expect
-# On branch master
-# Changes to be committed:
-# (use "git reset HEAD <file>..." to unstage)
-#
-# modified: dir1/modified
-#
-# Untracked files:
-# (use "git add <file>..." to include in what will be committed)
-#
-# dir1/untracked
-# dir2/
-# expect
-# output
-# untracked
-EOF
test_expect_success 'dry-run of partial commit excluding new file in index' '
+ cat >expect <<EOF &&
+On branch master
+Changes to be committed:
+ (use "git reset HEAD <file>..." to unstage)
+
+ modified: dir1/modified
+
+Untracked files:
+ (use "git add <file>..." to include in what will be committed)
+
+ dir1/untracked
+ dir2/
+ expect
+ output
+ untracked
+
+EOF
git commit --dry-run dir1/modified >output &&
test_i18ncmp expect output
'
git add sm
'
-cat >expect <<EOF
-# On branch master
-# Changes to be committed:
-# (use "git reset HEAD <file>..." to unstage)
-#
-# new file: dir2/added
-# new file: sm
-#
-# Changes not staged for commit:
-# (use "git add <file>..." to update what will be committed)
-# (use "git checkout -- <file>..." to discard changes in working directory)
-#
-# modified: dir1/modified
-#
-# Untracked files:
-# (use "git add <file>..." to include in what will be committed)
-#
-# dir1/untracked
-# dir2/modified
-# dir2/untracked
-# expect
-# output
-# untracked
-EOF
test_expect_success 'status submodule summary is disabled by default' '
+ cat >expect <<EOF &&
+On branch master
+Changes to be committed:
+ (use "git reset HEAD <file>..." to unstage)
+
+ new file: dir2/added
+ new file: sm
+
+Changes not staged for commit:
+ (use "git add <file>..." to update what will be committed)
+ (use "git checkout -- <file>..." to discard changes in working directory)
+
+ modified: dir1/modified
+
+Untracked files:
+ (use "git add <file>..." to include in what will be committed)
+
+ dir1/untracked
+ dir2/modified
+ dir2/untracked
+ expect
+ output
+ untracked
+
+EOF
git status >output &&
test_i18ncmp expect output
'
head=$(cd sm && git rev-parse --short=7 --verify HEAD)
-cat >expect <<EOF
-# On branch master
-# Changes to be committed:
-# (use "git reset HEAD <file>..." to unstage)
-#
-# new file: dir2/added
-# new file: sm
-#
-# Changes not staged for commit:
-# (use "git add <file>..." to update what will be committed)
-# (use "git checkout -- <file>..." to discard changes in working directory)
-#
-# modified: dir1/modified
-#
-# Submodule changes to be committed:
-#
-# * sm 0000000...$head (1):
-# > Add foo
-#
-# Untracked files:
-# (use "git add <file>..." to include in what will be committed)
-#
-# dir1/untracked
-# dir2/modified
-# dir2/untracked
-# expect
-# output
-# untracked
-EOF
test_expect_success 'status submodule summary' '
+ cat >expect <<EOF &&
+On branch master
+Changes to be committed:
+ (use "git reset HEAD <file>..." to unstage)
+
+ new file: dir2/added
+ new file: sm
+
+Changes not staged for commit:
+ (use "git add <file>..." to update what will be committed)
+ (use "git checkout -- <file>..." to discard changes in working directory)
+
+ modified: dir1/modified
+
+Submodule changes to be committed:
+
+* sm 0000000...$head (1):
+ > Add foo
+
+Untracked files:
+ (use "git add <file>..." to include in what will be committed)
+
+ dir1/untracked
+ dir2/modified
+ dir2/untracked
+ expect
+ output
+ untracked
+
+EOF
git config status.submodulesummary 10 &&
git status >output &&
test_i18ncmp expect output
'
+test_expect_success 'status submodule summary with status.displayCommentPrefix=false' '
+ strip_comments expect &&
+ git -c status.displayCommentPrefix=false status >output &&
+ test_i18ncmp expect output
+'
+
+test_expect_success 'commit with submodule summary ignores status.displayCommentPrefix' '
+ commit_template_commented
+'
+
cat >expect <<EOF
M dir1/modified
A dir2/added
test_cmp expect output
'
-cat >expect <<EOF
-# On branch master
-# Changes not staged for commit:
-# (use "git add <file>..." to update what will be committed)
-# (use "git checkout -- <file>..." to discard changes in working directory)
-#
-# modified: dir1/modified
-#
-# Untracked files:
-# (use "git add <file>..." to include in what will be committed)
-#
-# dir1/untracked
-# dir2/modified
-# dir2/untracked
-# expect
-# output
-# untracked
+test_expect_success 'status submodule summary (clean submodule): commit' '
+ cat >expect <<EOF &&
+On branch master
+Changes not staged for commit:
+ (use "git add <file>..." to update what will be committed)
+ (use "git checkout -- <file>..." to discard changes in working directory)
+
+ modified: dir1/modified
+
+Untracked files:
+ (use "git add <file>..." to include in what will be committed)
+
+ dir1/untracked
+ dir2/modified
+ dir2/untracked
+ expect
+ output
+ untracked
+
no changes added to commit (use "git add" and/or "git commit -a")
EOF
-test_expect_success 'status submodule summary (clean submodule): commit' '
git commit -m "commit submodule" &&
git config status.submodulesummary 10 &&
test_must_fail git commit --dry-run >output &&
test_cmp expect output
'
-cat >expect <<EOF
-# On branch master
-# Changes to be committed:
-# (use "git reset HEAD^1 <file>..." to unstage)
-#
-# new file: dir2/added
-# new file: sm
-#
-# Changes not staged for commit:
-# (use "git add <file>..." to update what will be committed)
-# (use "git checkout -- <file>..." to discard changes in working directory)
-#
-# modified: dir1/modified
-#
-# Submodule changes to be committed:
-#
-# * sm 0000000...$head (1):
-# > Add foo
-#
-# Untracked files:
-# (use "git add <file>..." to include in what will be committed)
-#
-# dir1/untracked
-# dir2/modified
-# dir2/untracked
-# expect
-# output
-# untracked
-EOF
test_expect_success 'commit --dry-run submodule summary (--amend)' '
+ cat >expect <<EOF &&
+On branch master
+Changes to be committed:
+ (use "git reset HEAD^1 <file>..." to unstage)
+
+ new file: dir2/added
+ new file: sm
+
+Changes not staged for commit:
+ (use "git add <file>..." to update what will be committed)
+ (use "git checkout -- <file>..." to discard changes in working directory)
+
+ modified: dir1/modified
+
+Submodule changes to be committed:
+
+* sm 0000000...$head (1):
+ > Add foo
+
+Untracked files:
+ (use "git add <file>..." to include in what will be committed)
+
+ dir1/untracked
+ dir2/modified
+ dir2/untracked
+ expect
+ output
+ untracked
+
+EOF
git config status.submodulesummary 10 &&
git commit --dry-run --amend >output &&
test_i18ncmp expect output
new_head=$(cd sm && git rev-parse --short=7 --verify HEAD)
touch .gitmodules
-cat > expect << EOF
-# On branch master
-# Changes to be committed:
-# (use "git reset HEAD <file>..." to unstage)
-#
-# modified: sm
-#
-# Changes not staged for commit:
-# (use "git add <file>..." to update what will be committed)
-# (use "git checkout -- <file>..." to discard changes in working directory)
-#
-# modified: dir1/modified
-#
-# Submodule changes to be committed:
-#
-# * sm $head...$new_head (1):
-# > Add bar
-#
-# Untracked files:
-# (use "git add <file>..." to include in what will be committed)
-#
-# .gitmodules
-# dir1/untracked
-# dir2/modified
-# dir2/untracked
-# expect
-# output
-# untracked
-EOF
-
test_expect_success '--ignore-submodules=untracked suppresses submodules with untracked content' '
+ cat > expect << EOF &&
+On branch master
+Changes to be committed:
+ (use "git reset HEAD <file>..." to unstage)
+
+ modified: sm
+
+Changes not staged for commit:
+ (use "git add <file>..." to update what will be committed)
+ (use "git checkout -- <file>..." to discard changes in working directory)
+
+ modified: dir1/modified
+
+Submodule changes to be committed:
+
+* sm $head...$new_head (1):
+ > Add bar
+
+Untracked files:
+ (use "git add <file>..." to include in what will be committed)
+
+ .gitmodules
+ dir1/untracked
+ dir2/modified
+ dir2/untracked
+ expect
+ output
+ untracked
+
+EOF
echo modified sm/untracked &&
git status --ignore-submodules=untracked >output &&
test_i18ncmp expect output
git config -f .gitmodules --remove-section submodule.subname
'
-cat > expect << EOF
-# On branch master
-# Changes to be committed:
-# (use "git reset HEAD <file>..." to unstage)
-#
-# modified: sm
-#
-# Changes not staged for commit:
-# (use "git add <file>..." to update what will be committed)
-# (use "git checkout -- <file>..." to discard changes in working directory)
-# (commit or discard the untracked or modified content in submodules)
-#
-# modified: dir1/modified
-# modified: sm (modified content)
-#
-# Submodule changes to be committed:
-#
-# * sm $head...$new_head (1):
-# > Add bar
-#
-# Untracked files:
-# (use "git add <file>..." to include in what will be committed)
-#
-# .gitmodules
-# dir1/untracked
-# dir2/modified
-# dir2/untracked
-# expect
-# output
-# untracked
-EOF
-
test_expect_success "--ignore-submodules=untracked doesn't suppress submodules with modified content" '
+ cat > expect << EOF &&
+On branch master
+Changes to be committed:
+ (use "git reset HEAD <file>..." to unstage)
+
+ modified: sm
+
+Changes not staged for commit:
+ (use "git add <file>..." to update what will be committed)
+ (use "git checkout -- <file>..." to discard changes in working directory)
+ (commit or discard the untracked or modified content in submodules)
+
+ modified: dir1/modified
+ modified: sm (modified content)
+
+Submodule changes to be committed:
+
+* sm $head...$new_head (1):
+ > Add bar
+
+Untracked files:
+ (use "git add <file>..." to include in what will be committed)
+
+ .gitmodules
+ dir1/untracked
+ dir2/modified
+ dir2/untracked
+ expect
+ output
+ untracked
+
+EOF
git status --ignore-submodules=untracked > output &&
test_i18ncmp expect output
'
head2=$(cd sm && git commit -q -m "2nd commit" foo && git rev-parse --short=7 --verify HEAD)
-cat > expect << EOF
-# On branch master
-# Changes to be committed:
-# (use "git reset HEAD <file>..." to unstage)
-#
-# modified: sm
-#
-# Changes not staged for commit:
-# (use "git add <file>..." to update what will be committed)
-# (use "git checkout -- <file>..." to discard changes in working directory)
-#
-# modified: dir1/modified
-# modified: sm (new commits)
-#
-# Submodule changes to be committed:
-#
-# * sm $head...$new_head (1):
-# > Add bar
-#
-# Submodules changed but not updated:
-#
-# * sm $new_head...$head2 (1):
-# > 2nd commit
-#
-# Untracked files:
-# (use "git add <file>..." to include in what will be committed)
-#
-# .gitmodules
-# dir1/untracked
-# dir2/modified
-# dir2/untracked
-# expect
-# output
-# untracked
-EOF
-
test_expect_success "--ignore-submodules=untracked doesn't suppress submodule summary" '
+ cat > expect << EOF &&
+On branch master
+Changes to be committed:
+ (use "git reset HEAD <file>..." to unstage)
+
+ modified: sm
+
+Changes not staged for commit:
+ (use "git add <file>..." to update what will be committed)
+ (use "git checkout -- <file>..." to discard changes in working directory)
+
+ modified: dir1/modified
+ modified: sm (new commits)
+
+Submodule changes to be committed:
+
+* sm $head...$new_head (1):
+ > Add bar
+
+Submodules changed but not updated:
+
+* sm $new_head...$head2 (1):
+ > 2nd commit
+
+Untracked files:
+ (use "git add <file>..." to include in what will be committed)
+
+ .gitmodules
+ dir1/untracked
+ dir2/modified
+ dir2/untracked
+ expect
+ output
+ untracked
+
+EOF
git status --ignore-submodules=untracked > output &&
test_i18ncmp expect output
'
; expect
; output
; untracked
+;
EOF
test_expect_success "status (core.commentchar with submodule summary)" '
test_config core.commentchar ";" &&
- git status >output &&
+ git -c status.displayCommentPrefix=true status >output &&
test_i18ncmp expect output
'
test_expect_success "status (core.commentchar with two chars with submodule summary)" '
test_config core.commentchar ";;" &&
- git status >output &&
+ git -c status.displayCommentPrefix=true status >output &&
test_i18ncmp expect output
'
-cat > expect << EOF
-# On branch master
-# Changes not staged for commit:
-# (use "git add <file>..." to update what will be committed)
-# (use "git checkout -- <file>..." to discard changes in working directory)
-#
-# modified: dir1/modified
-#
-# Untracked files:
-# (use "git add <file>..." to include in what will be committed)
-#
-# .gitmodules
-# dir1/untracked
-# dir2/modified
-# dir2/untracked
-# expect
-# output
-# untracked
+test_expect_success "--ignore-submodules=all suppresses submodule summary" '
+ cat > expect << EOF &&
+On branch master
+Changes not staged for commit:
+ (use "git add <file>..." to update what will be committed)
+ (use "git checkout -- <file>..." to discard changes in working directory)
+
+ modified: dir1/modified
+
+Untracked files:
+ (use "git add <file>..." to include in what will be committed)
+
+ .gitmodules
+ dir1/untracked
+ dir2/modified
+ dir2/untracked
+ expect
+ output
+ untracked
+
no changes added to commit (use "git add" and/or "git commit -a")
EOF
-
-test_expect_success "--ignore-submodules=all suppresses submodule summary" '
git status --ignore-submodules=all > output &&
test_i18ncmp expect output
'
test_expect_success 'status when conflicts unresolved' '
test_must_fail git merge master &&
- cat >expected <<-\EOF &&
- # On branch conflicts
- # You have unmerged paths.
- # (fix conflicts and run "git commit")
- #
- # Unmerged paths:
- # (use "git add <file>..." to mark resolution)
- #
- # both modified: main.txt
- #
- no changes added to commit (use "git add" and/or "git commit -a")
- EOF
+ cat >expected <<\EOF &&
+On branch conflicts
+You have unmerged paths.
+ (fix conflicts and run "git commit")
+
+Unmerged paths:
+ (use "git add <file>..." to mark resolution)
+
+ both modified: main.txt
+
+no changes added to commit (use "git add" and/or "git commit -a")
+EOF
git status --untracked-files=no >actual &&
test_i18ncmp expected actual
'
test_must_fail git merge master &&
echo one >main.txt &&
git add main.txt &&
- cat >expected <<-\EOF &&
- # On branch conflicts
- # All conflicts fixed but you are still merging.
- # (use "git commit" to conclude merge)
- #
- # Changes to be committed:
- #
- # modified: main.txt
- #
- # Untracked files not listed (use -u option to show untracked files)
- EOF
+ cat >expected <<\EOF &&
+On branch conflicts
+All conflicts fixed but you are still merging.
+ (use "git commit" to conclude merge)
+
+Changes to be committed:
+
+ modified: main.txt
+
+Untracked files not listed (use -u option to show untracked files)
+EOF
git status --untracked-files=no >actual &&
test_i18ncmp expected actual
'
test_when_finished "git rebase --abort" &&
ONTO=$(git rev-parse --short HEAD^^) &&
test_must_fail git rebase HEAD^ --onto HEAD^^ &&
- cat >expected <<-EOF &&
- # rebase in progress; onto $ONTO
- # You are currently rebasing branch '\''rebase_conflicts'\'' on '\''$ONTO'\''.
- # (fix conflicts and then run "git rebase --continue")
- # (use "git rebase --skip" to skip this patch)
- # (use "git rebase --abort" to check out the original branch)
- #
- # Unmerged paths:
- # (use "git reset HEAD <file>..." to unstage)
- # (use "git add <file>..." to mark resolution)
- #
- # both modified: main.txt
- #
- no changes added to commit (use "git add" and/or "git commit -a")
- EOF
+ cat >expected <<EOF &&
+rebase in progress; onto $ONTO
+You are currently rebasing branch '\''rebase_conflicts'\'' on '\''$ONTO'\''.
+ (fix conflicts and then run "git rebase --continue")
+ (use "git rebase --skip" to skip this patch)
+ (use "git rebase --abort" to check out the original branch)
+
+Unmerged paths:
+ (use "git reset HEAD <file>..." to unstage)
+ (use "git add <file>..." to mark resolution)
+
+ both modified: main.txt
+
+no changes added to commit (use "git add" and/or "git commit -a")
+EOF
git status --untracked-files=no >actual &&
test_i18ncmp expected actual
'
test_must_fail git rebase HEAD^ --onto HEAD^^ &&
echo three >main.txt &&
git add main.txt &&
- cat >expected <<-EOF &&
- # rebase in progress; onto $ONTO
- # You are currently rebasing branch '\''rebase_conflicts'\'' on '\''$ONTO'\''.
- # (all conflicts fixed: run "git rebase --continue")
- #
- # Changes to be committed:
- # (use "git reset HEAD <file>..." to unstage)
- #
- # modified: main.txt
- #
- # Untracked files not listed (use -u option to show untracked files)
- EOF
+ cat >expected <<EOF &&
+rebase in progress; onto $ONTO
+You are currently rebasing branch '\''rebase_conflicts'\'' on '\''$ONTO'\''.
+ (all conflicts fixed: run "git rebase --continue")
+
+Changes to be committed:
+ (use "git reset HEAD <file>..." to unstage)
+
+ modified: main.txt
+
+Untracked files not listed (use -u option to show untracked files)
+EOF
git status --untracked-files=no >actual &&
test_i18ncmp expected actual
'
test_when_finished "git rebase --abort" &&
ONTO=$(git rev-parse --short rebase_i_conflicts) &&
test_must_fail git rebase -i rebase_i_conflicts &&
- cat >expected <<-EOF &&
- # rebase in progress; onto $ONTO
- # You are currently rebasing branch '\''rebase_i_conflicts_second'\'' on '\''$ONTO'\''.
- # (fix conflicts and then run "git rebase --continue")
- # (use "git rebase --skip" to skip this patch)
- # (use "git rebase --abort" to check out the original branch)
- #
- # Unmerged paths:
- # (use "git reset HEAD <file>..." to unstage)
- # (use "git add <file>..." to mark resolution)
- #
- # both modified: main.txt
- #
- no changes added to commit (use "git add" and/or "git commit -a")
- EOF
+ cat >expected <<EOF &&
+rebase in progress; onto $ONTO
+You are currently rebasing branch '\''rebase_i_conflicts_second'\'' on '\''$ONTO'\''.
+ (fix conflicts and then run "git rebase --continue")
+ (use "git rebase --skip" to skip this patch)
+ (use "git rebase --abort" to check out the original branch)
+
+Unmerged paths:
+ (use "git reset HEAD <file>..." to unstage)
+ (use "git add <file>..." to mark resolution)
+
+ both modified: main.txt
+
+no changes added to commit (use "git add" and/or "git commit -a")
+EOF
git status --untracked-files=no >actual &&
test_i18ncmp expected actual
'
ONTO=$(git rev-parse --short rebase_i_conflicts) &&
test_must_fail git rebase -i rebase_i_conflicts &&
git add main.txt &&
- cat >expected <<-EOF &&
- # rebase in progress; onto $ONTO
- # You are currently rebasing branch '\''rebase_i_conflicts_second'\'' on '\''$ONTO'\''.
- # (all conflicts fixed: run "git rebase --continue")
- #
- # Changes to be committed:
- # (use "git reset HEAD <file>..." to unstage)
- #
- # modified: main.txt
- #
- # Untracked files not listed (use -u option to show untracked files)
- EOF
+ cat >expected <<EOF &&
+rebase in progress; onto $ONTO
+You are currently rebasing branch '\''rebase_i_conflicts_second'\'' on '\''$ONTO'\''.
+ (all conflicts fixed: run "git rebase --continue")
+
+Changes to be committed:
+ (use "git reset HEAD <file>..." to unstage)
+
+ modified: main.txt
+
+Untracked files not listed (use -u option to show untracked files)
+EOF
git status --untracked-files=no >actual &&
test_i18ncmp expected actual
'
test_when_finished "git rebase --abort" &&
ONTO=$(git rev-parse --short HEAD~2) &&
git rebase -i HEAD~2 &&
- cat >expected <<-EOF &&
- # rebase in progress; onto $ONTO
- # You are currently editing a commit while rebasing branch '\''rebase_i_edit'\'' on '\''$ONTO'\''.
- # (use "git commit --amend" to amend the current commit)
- # (use "git rebase --continue" once you are satisfied with your changes)
- #
- nothing to commit (use -u to show untracked files)
- EOF
+ cat >expected <<EOF &&
+rebase in progress; onto $ONTO
+You are currently editing a commit while rebasing branch '\''rebase_i_edit'\'' on '\''$ONTO'\''.
+ (use "git commit --amend" to amend the current commit)
+ (use "git rebase --continue" once you are satisfied with your changes)
+
+nothing to commit (use -u to show untracked files)
+EOF
git status --untracked-files=no >actual &&
test_i18ncmp expected actual
'
ONTO=$(git rev-parse --short HEAD~3) &&
git rebase -i HEAD~3 &&
git reset HEAD^ &&
- cat >expected <<-EOF &&
- # rebase in progress; onto $ONTO
- # You are currently splitting a commit while rebasing branch '\''split_commit'\'' on '\''$ONTO'\''.
- # (Once your working directory is clean, run "git rebase --continue")
- #
- # Changes not staged for commit:
- # (use "git add <file>..." to update what will be committed)
- # (use "git checkout -- <file>..." to discard changes in working directory)
- #
- # modified: main.txt
- #
- no changes added to commit (use "git add" and/or "git commit -a")
- EOF
+ cat >expected <<EOF &&
+rebase in progress; onto $ONTO
+You are currently splitting a commit while rebasing branch '\''split_commit'\'' on '\''$ONTO'\''.
+ (Once your working directory is clean, run "git rebase --continue")
+
+Changes not staged for commit:
+ (use "git add <file>..." to update what will be committed)
+ (use "git checkout -- <file>..." to discard changes in working directory)
+
+ modified: main.txt
+
+no changes added to commit (use "git add" and/or "git commit -a")
+EOF
git status --untracked-files=no >actual &&
test_i18ncmp expected actual
'
ONTO=$(git rev-parse --short HEAD~3) &&
git rebase -i HEAD~3 &&
git commit --amend -m "foo" &&
- cat >expected <<-EOF &&
- # rebase in progress; onto $ONTO
- # You are currently editing a commit while rebasing branch '\''amend_last'\'' on '\''$ONTO'\''.
- # (use "git commit --amend" to amend the current commit)
- # (use "git rebase --continue" once you are satisfied with your changes)
- #
- nothing to commit (use -u to show untracked files)
- EOF
+ cat >expected <<EOF &&
+rebase in progress; onto $ONTO
+You are currently editing a commit while rebasing branch '\''amend_last'\'' on '\''$ONTO'\''.
+ (use "git commit --amend" to amend the current commit)
+ (use "git rebase --continue" once you are satisfied with your changes)
+
+nothing to commit (use -u to show untracked files)
+EOF
git status --untracked-files=no >actual &&
test_i18ncmp expected actual
'
ONTO=$(git rev-parse --short HEAD~3) &&
git rebase -i HEAD~3 &&
git rebase --continue &&
- cat >expected <<-EOF &&
- # rebase in progress; onto $ONTO
- # You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
- # (use "git commit --amend" to amend the current commit)
- # (use "git rebase --continue" once you are satisfied with your changes)
- #
- nothing to commit (use -u to show untracked files)
- EOF
+ cat >expected <<EOF &&
+rebase in progress; onto $ONTO
+You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
+ (use "git commit --amend" to amend the current commit)
+ (use "git rebase --continue" once you are satisfied with your changes)
+
+nothing to commit (use -u to show untracked files)
+EOF
git status --untracked-files=no >actual &&
test_i18ncmp expected actual
'
git rebase -i HEAD~3 &&
git rebase --continue &&
git reset HEAD^ &&
- cat >expected <<-EOF &&
- # rebase in progress; onto $ONTO
- # You are currently splitting a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
- # (Once your working directory is clean, run "git rebase --continue")
- #
- # Changes not staged for commit:
- # (use "git add <file>..." to update what will be committed)
- # (use "git checkout -- <file>..." to discard changes in working directory)
- #
- # modified: main.txt
- #
- no changes added to commit (use "git add" and/or "git commit -a")
- EOF
+ cat >expected <<EOF &&
+rebase in progress; onto $ONTO
+You are currently splitting a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
+ (Once your working directory is clean, run "git rebase --continue")
+
+Changes not staged for commit:
+ (use "git add <file>..." to update what will be committed)
+ (use "git checkout -- <file>..." to discard changes in working directory)
+
+ modified: main.txt
+
+no changes added to commit (use "git add" and/or "git commit -a")
+EOF
git status --untracked-files=no >actual &&
test_i18ncmp expected actual
'
git rebase -i HEAD~3 &&
git rebase --continue &&
git commit --amend -m "foo" &&
- cat >expected <<-EOF &&
- # rebase in progress; onto $ONTO
- # You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
- # (use "git commit --amend" to amend the current commit)
- # (use "git rebase --continue" once you are satisfied with your changes)
- #
- nothing to commit (use -u to show untracked files)
- EOF
+ cat >expected <<EOF &&
+rebase in progress; onto $ONTO
+You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
+ (use "git commit --amend" to amend the current commit)
+ (use "git rebase --continue" once you are satisfied with your changes)
+
+nothing to commit (use -u to show untracked files)
+EOF
git status --untracked-files=no >actual &&
test_i18ncmp expected actual
'
git rebase -i HEAD~3 &&
git commit --amend -m "a" &&
git rebase --continue &&
- cat >expected <<-EOF &&
- # rebase in progress; onto $ONTO
- # You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
- # (use "git commit --amend" to amend the current commit)
- # (use "git rebase --continue" once you are satisfied with your changes)
- #
- nothing to commit (use -u to show untracked files)
- EOF
+ cat >expected <<EOF &&
+rebase in progress; onto $ONTO
+You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
+ (use "git commit --amend" to amend the current commit)
+ (use "git rebase --continue" once you are satisfied with your changes)
+
+nothing to commit (use -u to show untracked files)
+EOF
git status --untracked-files=no >actual &&
test_i18ncmp expected actual
'
git commit --amend -m "b" &&
git rebase --continue &&
git reset HEAD^ &&
- cat >expected <<-EOF &&
- # rebase in progress; onto $ONTO
- # You are currently splitting a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
- # (Once your working directory is clean, run "git rebase --continue")
- #
- # Changes not staged for commit:
- # (use "git add <file>..." to update what will be committed)
- # (use "git checkout -- <file>..." to discard changes in working directory)
- #
- # modified: main.txt
- #
- no changes added to commit (use "git add" and/or "git commit -a")
- EOF
+ cat >expected <<EOF &&
+rebase in progress; onto $ONTO
+You are currently splitting a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
+ (Once your working directory is clean, run "git rebase --continue")
+
+Changes not staged for commit:
+ (use "git add <file>..." to update what will be committed)
+ (use "git checkout -- <file>..." to discard changes in working directory)
+
+ modified: main.txt
+
+no changes added to commit (use "git add" and/or "git commit -a")
+EOF
git status --untracked-files=no >actual &&
test_i18ncmp expected actual
'
git commit --amend -m "c" &&
git rebase --continue &&
git commit --amend -m "d" &&
- cat >expected <<-EOF &&
- # rebase in progress; onto $ONTO
- # You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
- # (use "git commit --amend" to amend the current commit)
- # (use "git rebase --continue" once you are satisfied with your changes)
- #
- nothing to commit (use -u to show untracked files)
- EOF
+ cat >expected <<EOF &&
+rebase in progress; onto $ONTO
+You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
+ (use "git commit --amend" to amend the current commit)
+ (use "git rebase --continue" once you are satisfied with your changes)
+
+nothing to commit (use -u to show untracked files)
+EOF
git status --untracked-files=no >actual &&
test_i18ncmp expected actual
'
git add main.txt &&
git commit -m "e" &&
git rebase --continue &&
- cat >expected <<-EOF &&
- # rebase in progress; onto $ONTO
- # You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
- # (use "git commit --amend" to amend the current commit)
- # (use "git rebase --continue" once you are satisfied with your changes)
- #
- nothing to commit (use -u to show untracked files)
- EOF
+ cat >expected <<EOF &&
+rebase in progress; onto $ONTO
+You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
+ (use "git commit --amend" to amend the current commit)
+ (use "git rebase --continue" once you are satisfied with your changes)
+
+nothing to commit (use -u to show untracked files)
+EOF
git status --untracked-files=no >actual &&
test_i18ncmp expected actual
'
git commit --amend -m "f" &&
git rebase --continue &&
git reset HEAD^ &&
- cat >expected <<-EOF &&
- # rebase in progress; onto $ONTO
- # You are currently splitting a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
- # (Once your working directory is clean, run "git rebase --continue")
- #
- # Changes not staged for commit:
- # (use "git add <file>..." to update what will be committed)
- # (use "git checkout -- <file>..." to discard changes in working directory)
- #
- # modified: main.txt
- #
- no changes added to commit (use "git add" and/or "git commit -a")
- EOF
+ cat >expected <<EOF &&
+rebase in progress; onto $ONTO
+You are currently splitting a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
+ (Once your working directory is clean, run "git rebase --continue")
+
+Changes not staged for commit:
+ (use "git add <file>..." to update what will be committed)
+ (use "git checkout -- <file>..." to discard changes in working directory)
+
+ modified: main.txt
+
+no changes added to commit (use "git add" and/or "git commit -a")
+EOF
git status --untracked-files=no >actual &&
test_i18ncmp expected actual
'
git commit --amend -m "g" &&
git rebase --continue &&
git commit --amend -m "h" &&
- cat >expected <<-EOF &&
- # rebase in progress; onto $ONTO
- # You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
- # (use "git commit --amend" to amend the current commit)
- # (use "git rebase --continue" once you are satisfied with your changes)
- #
- nothing to commit (use -u to show untracked files)
- EOF
+ cat >expected <<EOF &&
+rebase in progress; onto $ONTO
+You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
+ (use "git commit --amend" to amend the current commit)
+ (use "git rebase --continue" once you are satisfied with your changes)
+
+nothing to commit (use -u to show untracked files)
+EOF
git status --untracked-files=no >actual &&
test_i18ncmp expected actual
'
test_when_finished "rm Maildir/* && git am --abort" &&
git format-patch -1 -oMaildir &&
test_must_fail git am Maildir/*.patch &&
- cat >expected <<-\EOF &&
- # On branch am_already_exists
- # You are in the middle of an am session.
- # (fix conflicts and then run "git am --continue")
- # (use "git am --skip" to skip this patch)
- # (use "git am --abort" to restore the original branch)
- #
- nothing to commit (use -u to show untracked files)
- EOF
+ cat >expected <<\EOF &&
+On branch am_already_exists
+You are in the middle of an am session.
+ (fix conflicts and then run "git am --continue")
+ (use "git am --skip" to skip this patch)
+ (use "git am --abort" to restore the original branch)
+
+nothing to commit (use -u to show untracked files)
+EOF
git status --untracked-files=no >actual &&
test_i18ncmp expected actual
'
test_when_finished "rm Maildir/* && git am --abort" &&
git format-patch -1 -oMaildir &&
test_must_fail git am Maildir/*.patch &&
- cat >expected <<-\EOF &&
- # On branch am_not_exists
- # You are in the middle of an am session.
- # (fix conflicts and then run "git am --continue")
- # (use "git am --skip" to skip this patch)
- # (use "git am --abort" to restore the original branch)
- #
- nothing to commit (use -u to show untracked files)
- EOF
+ cat >expected <<\EOF &&
+On branch am_not_exists
+You are in the middle of an am session.
+ (fix conflicts and then run "git am --continue")
+ (use "git am --skip" to skip this patch)
+ (use "git am --abort" to restore the original branch)
+
+nothing to commit (use -u to show untracked files)
+EOF
git status --untracked-files=no >actual &&
test_i18ncmp expected actual
'
git commit -m "delete all am_empty" &&
echo error >Maildir/0002-two_am.patch &&
test_must_fail git am Maildir/*.patch &&
- cat >expected <<-\EOF &&
- # On branch am_empty
- # You are in the middle of an am session.
- # The current patch is empty.
- # (use "git am --skip" to skip this patch)
- # (use "git am --abort" to restore the original branch)
- #
- nothing to commit (use -u to show untracked files)
- EOF
+ cat >expected <<\EOF &&
+On branch am_empty
+You are in the middle of an am session.
+The current patch is empty.
+ (use "git am --skip" to skip this patch)
+ (use "git am --abort" to restore the original branch)
+
+nothing to commit (use -u to show untracked files)
+EOF
git status --untracked-files=no >actual &&
test_i18ncmp expected actual
'
git bisect bad &&
git bisect good one_bisect &&
TGT=$(git rev-parse --short two_bisect) &&
- cat >expected <<-EOF &&
- # HEAD detached at $TGT
- # You are currently bisecting, started from branch '\''bisect'\''.
- # (use "git bisect reset" to get back to the original branch)
- #
- nothing to commit (use -u to show untracked files)
- EOF
+ cat >expected <<EOF &&
+HEAD detached at $TGT
+You are currently bisecting, started from branch '\''bisect'\''.
+ (use "git bisect reset" to get back to the original branch)
+
+nothing to commit (use -u to show untracked files)
+EOF
git status --untracked-files=no >actual &&
test_i18ncmp expected actual
'
test_when_finished "git rebase --abort" &&
ONTO=$(git rev-parse --short HEAD^^) &&
test_must_fail git rebase HEAD^ --onto HEAD^^ &&
- cat >expected <<-EOF &&
- # rebase in progress; onto $ONTO
- # You are currently rebasing branch '\''statushints_disabled'\'' on '\''$ONTO'\''.
- #
- # Unmerged paths:
- # both modified: main.txt
- #
- no changes added to commit
- EOF
+ cat >expected <<EOF &&
+rebase in progress; onto $ONTO
+You are currently rebasing branch '\''statushints_disabled'\'' on '\''$ONTO'\''.
+
+Unmerged paths:
+ both modified: main.txt
+
+no changes added to commit
+EOF
git status --untracked-files=no >actual &&
test_i18ncmp expected actual
'
test_expect_success 'status when cherry-picking before resolving conflicts' '
test_when_finished "git cherry-pick --abort" &&
test_must_fail git cherry-pick cherry_branch_second &&
- cat >expected <<-\EOF &&
- # On branch cherry_branch
- # You are currently cherry-picking.
- # (fix conflicts and run "git cherry-pick --continue")
- # (use "git cherry-pick --abort" to cancel the cherry-pick operation)
- #
- # Unmerged paths:
- # (use "git add <file>..." to mark resolution)
- #
- # both modified: main.txt
- #
- no changes added to commit (use "git add" and/or "git commit -a")
- EOF
+ cat >expected <<\EOF &&
+On branch cherry_branch
+You are currently cherry-picking.
+ (fix conflicts and run "git cherry-pick --continue")
+ (use "git cherry-pick --abort" to cancel the cherry-pick operation)
+
+Unmerged paths:
+ (use "git add <file>..." to mark resolution)
+
+ both modified: main.txt
+
+no changes added to commit (use "git add" and/or "git commit -a")
+EOF
git status --untracked-files=no >actual &&
test_i18ncmp expected actual
'
test_must_fail git cherry-pick cherry_branch_second &&
echo end >main.txt &&
git add main.txt &&
- cat >expected <<-\EOF &&
- # On branch cherry_branch
- # You are currently cherry-picking.
- # (all conflicts fixed: run "git cherry-pick --continue")
- # (use "git cherry-pick --abort" to cancel the cherry-pick operation)
- #
- # Changes to be committed:
- #
- # modified: main.txt
- #
- # Untracked files not listed (use -u option to show untracked files)
- EOF
+ cat >expected <<\EOF &&
+On branch cherry_branch
+You are currently cherry-picking.
+ (all conflicts fixed: run "git cherry-pick --continue")
+ (use "git cherry-pick --abort" to cancel the cherry-pick operation)
+
+Changes to be committed:
+
+ modified: main.txt
+
+Untracked files not listed (use -u option to show untracked files)
+EOF
git status --untracked-files=no >actual &&
test_i18ncmp expected actual
'
test_expect_success 'status showing detached at and from a tag' '
test_commit atag tagging &&
git checkout atag &&
- cat >expected <<-\EOF
- # HEAD detached at atag
- nothing to commit (use -u to show untracked files)
- EOF
+ cat >expected <<\EOF
+HEAD detached at atag
+nothing to commit (use -u to show untracked files)
+EOF
git status --untracked-files=no >actual &&
test_i18ncmp expected actual &&
git reset --hard HEAD^ &&
- cat >expected <<-\EOF
- # HEAD detached from atag
- nothing to commit (use -u to show untracked files)
- EOF
+ cat >expected <<\EOF
+HEAD detached from atag
+nothing to commit (use -u to show untracked files)
+EOF
git status --untracked-files=no >actual &&
test_i18ncmp expected actual
'
test_commit new to-revert.txt &&
TO_REVERT=$(git rev-parse --short HEAD^) &&
test_must_fail git revert $TO_REVERT &&
- cat >expected <<-EOF
- # On branch master
- # You are currently reverting commit $TO_REVERT.
- # (fix conflicts and run "git revert --continue")
- # (use "git revert --abort" to cancel the revert operation)
- #
- # Unmerged paths:
- # (use "git reset HEAD <file>..." to unstage)
- # (use "git add <file>..." to mark resolution)
- #
- # both modified: to-revert.txt
- #
- no changes added to commit (use "git add" and/or "git commit -a")
- EOF
+ cat >expected <<EOF
+On branch master
+You are currently reverting commit $TO_REVERT.
+ (fix conflicts and run "git revert --continue")
+ (use "git revert --abort" to cancel the revert operation)
+
+Unmerged paths:
+ (use "git reset HEAD <file>..." to unstage)
+ (use "git add <file>..." to mark resolution)
+
+ both modified: to-revert.txt
+
+no changes added to commit (use "git add" and/or "git commit -a")
+EOF
git status --untracked-files=no >actual &&
test_i18ncmp expected actual
'
test_expect_success 'status while reverting commit (conflicts resolved)' '
echo reverted >to-revert.txt &&
git add to-revert.txt &&
- cat >expected <<-EOF
- # On branch master
- # You are currently reverting commit $TO_REVERT.
- # (all conflicts fixed: run "git revert --continue")
- # (use "git revert --abort" to cancel the revert operation)
- #
- # Changes to be committed:
- # (use "git reset HEAD <file>..." to unstage)
- #
- # modified: to-revert.txt
- #
- # Untracked files not listed (use -u option to show untracked files)
- EOF
+ cat >expected <<EOF
+On branch master
+You are currently reverting commit $TO_REVERT.
+ (all conflicts fixed: run "git revert --continue")
+ (use "git revert --abort" to cancel the revert operation)
+
+Changes to be committed:
+ (use "git reset HEAD <file>..." to unstage)
+
+ modified: to-revert.txt
+
+Untracked files not listed (use -u option to show untracked files)
+EOF
git status --untracked-files=no >actual &&
test_i18ncmp expected actual
'
test_expect_success 'status after reverting commit' '
git revert --continue &&
- cat >expected <<-\EOF
- # On branch master
- nothing to commit (use -u to show untracked files)
- EOF
+ cat >expected <<\EOF
+On branch master
+nothing to commit (use -u to show untracked files)
+EOF
git status --untracked-files=no >actual &&
test_i18ncmp expected actual
'
git checkout -b test6 branch1 &&
git submodule update -N &&
mv submod submod-movedaside &&
- git rm submod &&
+ git rm --cached submod &&
git commit -m "Submodule deleted from branch" &&
git checkout -b test6.a test6 &&
test_must_fail git merge master &&
git checkout -b test7 branch1 &&
git submodule update -N &&
mv submod submod-movedaside &&
- git rm submod &&
+ git rm --cached submod &&
echo not a submodule >submod &&
git add submod &&
git commit -m "Submodule path becomes file" &&
test_expect_success 'directory vs modified submodule' '
git checkout -b test11 branch1 &&
mv submod submod-movedaside &&
- git rm submod &&
+ git rm --cached submod &&
mkdir submod &&
echo not a submodule >submod/file16 &&
git add submod/file16 &&
'
#
-# notemodify, mark in committish
+# notemodify, mark in commit-ish
#
-test_expect_success 'S: notemodify with garbarge after mark committish must fail' '
+test_expect_success 'S: notemodify with garbarge after mark commit-ish must fail' '
test_must_fail git fast-import --import-marks=marks <<-EOF 2>err &&
commit refs/heads/Snotes
committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
data <<COMMIT
- commit S note committish
+ commit S note commit-ish
COMMIT
N :202 :302x
EOF
error "You haven't built things yet, have you?"
}
-if test -z "$GIT_TEST_INSTALLED" && test -z "$NO_PYTHON"
-then
- GITPYTHONLIB="$GIT_BUILD_DIR/git_remote_helpers/build/lib"
- export GITPYTHONLIB
- test -d "$GIT_BUILD_DIR"/git_remote_helpers/build || {
- error "You haven't built git_remote_helpers yet, have you?"
- }
-fi
-
if ! test -x "$GIT_BUILD_DIR"/test-chmtime
then
echo >&2 'You need to build test-chmtime:'
die("cannot parse %s as an object name", av[2]);
one = parse_tree_indirect(hash1);
if (!one)
- die("not a treeish %s", av[1]);
+ die("not a tree-ish %s", av[1]);
two = parse_tree_indirect(hash2);
if (!two)
- die("not a treeish %s", av[2]);
+ die("not a tree-ish %s", av[2]);
shift_tree(one->object.sha1, two->object.sha1, shifted, -1);
printf("shifted: %s\n", sha1_to_hex(shifted));
git_SHA_CTX ctx;
unsigned char sha1[20];
unsigned bufsz = 8192;
+ int binary = 0;
char *buffer;
- if (ac == 2)
- bufsz = strtoul(av[1], NULL, 10) * 1024 * 1024;
+ if (ac == 2) {
+ if (!strcmp(av[1], "-b"))
+ binary = 1;
+ else
+ bufsz = strtoul(av[1], NULL, 10) * 1024 * 1024;
+ }
if (!bufsz)
bufsz = 8192;
git_SHA1_Update(&ctx, buffer, this_sz);
}
git_SHA1_Final(sha1, &ctx);
- puts(sha1_to_hex(sha1));
+
+ if (binary)
+ fwrite(sha1, 1, 20, stdout);
+ else
+ puts(sha1_to_hex(sha1));
exit(0);
}
--- /dev/null
+#include "git-compat-util.h"
+#include "urlmatch.h"
+
+int main(int argc, char **argv)
+{
+ const char usage[] = "test-urlmatch-normalization [-p | -l] <url1> | <url1> <url2>";
+ char *url1, *url2;
+ int opt_p = 0, opt_l = 0;
+
+ /*
+ * For one url, succeed if url_normalize succeeds on it, fail otherwise.
+ * For two urls, succeed only if url_normalize succeeds on both and
+ * the results compare equal with strcmp. If -p is given (one url only)
+ * and url_normalize succeeds, print the result followed by "\n". If
+ * -l is given (one url only) and url_normalize succeeds, print the
+ * returned length in decimal followed by "\n".
+ */
+
+ if (argc > 1 && !strcmp(argv[1], "-p")) {
+ opt_p = 1;
+ argc--;
+ argv++;
+ } else if (argc > 1 && !strcmp(argv[1], "-l")) {
+ opt_l = 1;
+ argc--;
+ argv++;
+ }
+
+ if (argc < 2 || argc > 3)
+ die("%s", usage);
+
+ if (argc == 2) {
+ struct url_info info;
+ url1 = url_normalize(argv[1], &info);
+ if (!url1)
+ return 1;
+ if (opt_p)
+ printf("%s\n", url1);
+ if (opt_l)
+ printf("%u\n", (unsigned)info.url_len);
+ return 0;
+ }
+
+ if (opt_p || opt_l)
+ die("%s", usage);
+
+ url1 = url_normalize(argv[1], NULL);
+ url2 = url_normalize(argv[2], NULL);
+ return (url1 && url2 && !strcmp(url1, url2)) ? 0 : 1;
+}
push : 1,
connect : 1,
signed_tags : 1,
- no_disconnect_req : 1;
+ check_connectivity : 1,
+ no_disconnect_req : 1,
+ no_private_update : 1;
char *export_marks;
char *import_marks;
/* These go from remote name (as in "list") to private name */
data->bidi_import = 1;
else if (!strcmp(capname, "export"))
data->export = 1;
+ else if (!strcmp(capname, "check-connectivity"))
+ data->check_connectivity = 1;
else if (!data->refspecs && !prefixcmp(capname, "refspec ")) {
ALLOC_GROW(refspecs,
refspec_nr + 1,
strbuf_addstr(&arg, "--import-marks=");
strbuf_addstr(&arg, capname + strlen("import-marks "));
data->import_marks = strbuf_detach(&arg, NULL);
+ } else if (!prefixcmp(capname, "no-private-update")) {
+ data->no_private_update = 1;
} else if (mandatory) {
die("Unknown mandatory capability %s. This remote "
"helper probably needs newer version of Git.",
struct strbuf buf = STRBUF_INIT;
standard_options(transport);
+ if (data->check_connectivity &&
+ data->transport_options.check_self_contained_and_connected)
+ set_helper_option(transport, "check-connectivity", "true");
for (i = 0; i < nr_heads; i++) {
const struct ref *posn = to_fetch[i];
else
transport->pack_lockfile = xstrdup(name);
}
+ else if (data->check_connectivity &&
+ data->transport_options.check_self_contained_and_connected &&
+ !strcmp(buf.buf, "connectivity-ok"))
+ data->transport_options.self_contained_and_connected = 1;
else if (!buf.len)
break;
else
free(msg);
msg = NULL;
}
+ else if (!strcmp(msg, "stale info")) {
+ status = REF_STATUS_REJECT_STALE;
+ free(msg);
+ msg = NULL;
+ }
}
if (*ref)
if (push_update_ref_status(&buf, &ref, remote_refs))
continue;
- if (!data->refspecs)
+ if (!data->refspecs || data->no_private_update)
continue;
/* propagate back the update to the remote namespace */
}
static int push_refs_with_push(struct transport *transport,
- struct ref *remote_refs, int flags)
+ struct ref *remote_refs, int flags)
{
int force_all = flags & TRANSPORT_PUSH_FORCE;
int mirror = flags & TRANSPORT_PUSH_MIRROR;
struct helper_data *data = transport->data;
struct strbuf buf = STRBUF_INIT;
struct ref *ref;
+ struct string_list cas_options = STRING_LIST_INIT_DUP;
+ struct string_list_item *cas_option;
get_helper(transport);
if (!data->push)
/* Check for statuses set by set_ref_status_for_push() */
switch (ref->status) {
case REF_STATUS_REJECT_NONFASTFORWARD:
+ case REF_STATUS_REJECT_STALE:
case REF_STATUS_REJECT_ALREADY_EXISTS:
case REF_STATUS_UPTODATE:
continue;
strbuf_addch(&buf, ':');
strbuf_addstr(&buf, ref->name);
strbuf_addch(&buf, '\n');
+
+ /*
+ * The "--force-with-lease" options without explicit
+ * values to expect have already been expanded into
+ * the ref->old_sha1_expect[] field; we can ignore
+ * transport->smart_options->cas altogether and instead
+ * can enumerate them from the refs.
+ */
+ if (ref->expect_old_sha1) {
+ struct strbuf cas = STRBUF_INIT;
+ strbuf_addf(&cas, "%s:%s",
+ ref->name, sha1_to_hex(ref->old_sha1_expect));
+ string_list_append(&cas_options, strbuf_detach(&cas, NULL));
+ }
}
- if (buf.len == 0)
+ if (buf.len == 0) {
+ string_list_clear(&cas_options, 0);
return 0;
+ }
standard_options(transport);
+ for_each_string_list_item(cas_option, &cas_options)
+ set_helper_option(transport, "cas", cas_option->string);
if (flags & TRANSPORT_PUSH_DRY_RUN) {
if (set_helper_option(transport, "dry-run", "true") != 0)
#include "run-command.h"
#include "pkt-line.h"
#include "fetch-pack.h"
+#include "remote.h"
+#include "connect.h"
#include "send-pack.h"
#include "walker.h"
#include "bundle.h"
print_ref_status('!', "[rejected]", ref, ref->peer_ref,
"needs force", porcelain);
break;
+ case REF_STATUS_REJECT_STALE:
+ print_ref_status('!', "[rejected]", ref, ref->peer_ref,
+ "stale info", porcelain);
+ break;
case REF_STATUS_REMOTE_REJECT:
print_ref_status('!', "[remote rejected]", ref,
ref->deletion ? NULL : ref->peer_ref,
transport->push_refs = git_transport_push;
transport->disconnect = disconnect_git;
transport->smart_options = &(data->options);
+
+ transport->cannot_reuse = 1;
}
static int is_local(const char *url)
for (r = remote_refs; r; r = r->next) {
if (!r->peer_ref) continue;
if (r->status == REF_STATUS_REJECT_NONFASTFORWARD) continue;
+ if (r->status == REF_STATUS_REJECT_STALE) continue;
if (r->status == REF_STATUS_UPTODATE) continue;
strbuf_reset(&buf);
return -1;
}
+ if (transport->smart_options &&
+ transport->smart_options->cas &&
+ !is_empty_cas(transport->smart_options->cas))
+ apply_push_cas(transport->smart_options->cas,
+ transport->remote, remote_refs);
+
set_ref_status_for_push(remote_refs,
flags & TRANSPORT_PUSH_MIRROR,
flags & TRANSPORT_PUSH_FORCE);
#define TRANSPORT_H
#include "cache.h"
+#include "run-command.h"
#include "remote.h"
struct git_transport_options {
int depth;
const char *uploadpack;
const char *receivepack;
+ struct push_cas_option *cas;
};
struct transport {
*/
unsigned got_remote_refs : 1;
+ /*
+ * Transports that call take-over destroys the data specific to
+ * the transport type while doing so, and cannot be reused.
+ */
+ unsigned cannot_reuse : 1;
+
/**
* Returns 0 if successful, positive if the option is not
* recognized or is inapplicable, and negative if the option
/* Transfer the data as a thin pack if not null */
#define TRANS_OPT_THIN "thin"
+/* Check the current value of the remote ref */
+#define TRANS_OPT_CAS "cas"
+
/* Keep the pack that was transferred if not null */
#define TRANS_OPT_KEEP "keep"
/* Enable recursion indefinitely */
opt->pathspec.recursive = DIFF_OPT_TST(opt, RECURSIVE);
- opt->pathspec.max_depth = -1;
strbuf_init(&base, PATH_MAX);
strbuf_add(&base, base_str, baselen);
struct diff_options diff_opts;
struct diff_queue_struct *q = &diff_queued_diff;
struct diff_filepair *choice;
- const char *paths[1];
int i;
+ /*
+ * follow-rename code is very specific, we need exactly one
+ * path. Magic that matches more than one path is not
+ * supported.
+ */
+ GUARD_PATHSPEC(&opt->pathspec, PATHSPEC_FROMTOP | PATHSPEC_LITERAL);
+#if 0
+ /*
+ * We should reject wildcards as well. Unfortunately we
+ * haven't got a reliable way to detect that 'foo\*bar' in
+ * fact has no wildcards. nowildcard_len is merely a hint for
+ * optimization. Let it slip for now until wildmatch is taught
+ * about dry-run mode and returns wildcard info.
+ */
+ if (opt->pathspec.has_wildcard)
+ die("BUG:%s:%d: wildcards are not supported",
+ __FILE__, __LINE__);
+#endif
+
/* Remove the file creation entry from the diff queue, and remember it */
choice = q->queue[0];
q->nr = 0;
DIFF_OPT_SET(&diff_opts, RECURSIVE);
DIFF_OPT_SET(&diff_opts, FIND_COPIES_HARDER);
diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
- diff_opts.single_follow = opt->pathspec.raw[0];
+ diff_opts.single_follow = opt->pathspec.items[0].match;
diff_opts.break_opt = opt->break_opt;
diff_opts.rename_score = opt->rename_score;
- paths[0] = NULL;
- diff_tree_setup_paths(paths, &diff_opts);
diff_setup_done(&diff_opts);
diff_tree(t1, t2, base, &diff_opts);
diffcore_std(&diff_opts);
- diff_tree_release_paths(&diff_opts);
+ free_pathspec(&diff_opts.pathspec);
/* Go through the new set of filepairing, and see if we find a more interesting one */
opt->found_follow = 0;
* the future!
*/
if ((p->status == 'R' || p->status == 'C') &&
- !strcmp(p->two->path, opt->pathspec.raw[0])) {
+ !strcmp(p->two->path, opt->pathspec.items[0].match)) {
+ const char *path[2];
+
/* Switch the file-pairs around */
q->queue[i] = choice;
choice = p;
/* Update the path we use from now on.. */
- diff_tree_release_paths(opt);
- opt->pathspec.raw[0] = xstrdup(p->one->path);
- diff_tree_setup_paths(opt->pathspec.raw, opt);
+ path[0] = p->one->path;
+ path[1] = NULL;
+ free_pathspec(&opt->pathspec);
+ parse_pathspec(&opt->pathspec, PATHSPEC_ALL_MAGIC, 0, "", path);
/*
* The caller expects us to return a set of vanilla
free(tree);
return retval;
}
-
-void diff_tree_release_paths(struct diff_options *opt)
-{
- free_pathspec(&opt->pathspec);
-}
-
-void diff_tree_setup_paths(const char **p, struct diff_options *opt)
-{
- init_pathspec(&opt->pathspec, p);
-}
#include "unpack-trees.h"
#include "dir.h"
#include "tree.h"
+#include "pathspec.h"
static const char *get_mode(const char *str, unsigned int *modep)
{
return retval;
}
-static int match_entry(const struct name_entry *entry, int pathlen,
+static int match_entry(const struct pathspec_item *item,
+ const struct name_entry *entry, int pathlen,
const char *match, int matchlen,
enum interesting *never_interesting)
{
int m = -1; /* signals that we haven't called strncmp() */
- if (*never_interesting != entry_not_interesting) {
+ if (item->magic & PATHSPEC_ICASE)
+ /*
+ * "Never interesting" trick requires exact
+ * matching. We could do something clever with inexact
+ * matching, but it's trickier (and not to forget that
+ * strcasecmp is locale-dependent, at least in
+ * glibc). Just disable it for now. It can't be worse
+ * than the wildcard's codepath of '[Tt][Hi][Is][Ss]'
+ * pattern.
+ */
+ *never_interesting = entry_not_interesting;
+ else if (*never_interesting != entry_not_interesting) {
/*
* We have not seen any match that sorts later
* than the current path.
* we cheated and did not do strncmp(), so we do
* that here.
*/
- m = strncmp(match, entry->path, pathlen);
+ m = ps_strncmp(item, match, entry->path, pathlen);
/*
* If common part matched earlier then it is a hit,
* leading directory and is shorter than match.
*/
if (!m)
+ /*
+ * match_entry does not check if the prefix part is
+ * matched case-sensitively. If the entry is a
+ * directory and part of prefix, it'll be rematched
+ * eventually by basecmp with special treatment for
+ * the prefix.
+ */
return 1;
return 0;
}
-static int match_dir_prefix(const char *base,
+/* :(icase)-aware string compare */
+static int basecmp(const struct pathspec_item *item,
+ const char *base, const char *match, int len)
+{
+ if (item->magic & PATHSPEC_ICASE) {
+ int ret, n = len > item->prefix ? item->prefix : len;
+ ret = strncmp(base, match, n);
+ if (ret)
+ return ret;
+ base += n;
+ match += n;
+ len -= n;
+ }
+ return ps_strncmp(item, base, match, len);
+}
+
+static int match_dir_prefix(const struct pathspec_item *item,
+ const char *base,
const char *match, int matchlen)
{
- if (strncmp(base, match, matchlen))
+ if (basecmp(item, base, match, matchlen))
return 0;
/*
*/
if (baselen >= matchlen) {
*matched = matchlen;
- return !strncmp(base, match, matchlen);
+ return !basecmp(item, base, match, matchlen);
}
dirlen = matchlen;
* base ends with '/' so we are sure it really matches
* directory
*/
- if (strncmp(base, match, baselen))
+ if (basecmp(item, base, match, baselen))
return 0;
*matched = baselen;
} else
enum interesting never_interesting = ps->has_wildcard ?
entry_not_interesting : all_entries_not_interesting;
+ GUARD_PATHSPEC(ps,
+ PATHSPEC_FROMTOP |
+ PATHSPEC_MAXDEPTH |
+ PATHSPEC_LITERAL |
+ PATHSPEC_GLOB |
+ PATHSPEC_ICASE);
+
if (!ps->nr) {
- if (!ps->recursive || ps->max_depth == -1)
+ if (!ps->recursive ||
+ !(ps->magic & PATHSPEC_MAXDEPTH) ||
+ ps->max_depth == -1)
return all_entries_interesting;
return within_depth(base->buf + base_offset, baselen,
!!S_ISDIR(entry->mode),
if (baselen >= matchlen) {
/* If it doesn't match, move along... */
- if (!match_dir_prefix(base_str, match, matchlen))
+ if (!match_dir_prefix(item, base_str, match, matchlen))
goto match_wildcards;
- if (!ps->recursive || ps->max_depth == -1)
+ if (!ps->recursive ||
+ !(ps->magic & PATHSPEC_MAXDEPTH) ||
+ ps->max_depth == -1)
return all_entries_interesting;
return within_depth(base_str + matchlen + 1,
}
/* Either there must be no base, or the base must match. */
- if (baselen == 0 || !strncmp(base_str, match, baselen)) {
- if (match_entry(entry, pathlen,
+ if (baselen == 0 || !basecmp(item, base_str, match, baselen)) {
+ if (match_entry(item, entry, pathlen,
match + baselen, matchlen - baselen,
&never_interesting))
return entry_interesting;
if (item->nowildcard_len < item->len) {
- if (!git_fnmatch(match + baselen, entry->path,
- item->flags & PATHSPEC_ONESTAR ? GFNM_ONESTAR : 0,
+ if (!git_fnmatch(item, match + baselen, entry->path,
item->nowildcard_len - baselen))
return entry_interesting;
strbuf_add(base, entry->path, pathlen);
- if (!git_fnmatch(match, base->buf + base_offset,
- item->flags & PATHSPEC_ONESTAR ? GFNM_ONESTAR : 0,
+ if (!git_fnmatch(item, match, base->buf + base_offset,
item->nowildcard_len)) {
strbuf_setlen(base, base_offset + baselen);
return entry_interesting;
}
static int read_tree_1(struct tree *tree, struct strbuf *base,
- int stage, struct pathspec *pathspec,
+ int stage, const struct pathspec *pathspec,
read_tree_fn_t fn, void *context)
{
struct tree_desc desc;
int read_tree_recursive(struct tree *tree,
const char *base, int baselen,
- int stage, struct pathspec *pathspec,
+ int stage, const struct pathspec *pathspec,
read_tree_fn_t fn, void *context)
{
struct strbuf sb = STRBUF_INIT;
return parse_tree_buffer(item, buffer, size);
}
+void free_tree_buffer(struct tree *tree)
+{
+ free(tree->buffer);
+ tree->buffer = NULL;
+ tree->size = 0;
+ tree->object.parsed = 0;
+}
+
struct tree *parse_tree_indirect(const unsigned char *sha1)
{
struct object *obj = parse_object(sha1);
int parse_tree_buffer(struct tree *item, void *buffer, unsigned long size);
int parse_tree(struct tree *tree);
+void free_tree_buffer(struct tree *tree);
/* Parses and returns the tree in the given ent, chasing tags and commits. */
struct tree *parse_tree_indirect(const unsigned char *sha1);
extern int read_tree_recursive(struct tree *tree,
const char *base, int baselen,
- int stage, struct pathspec *pathspec,
+ int stage, const struct pathspec *pathspec,
read_tree_fn_t fn, void *context);
extern int read_tree(struct tree *tree, int stage, struct pathspec *pathspec);
#include "revision.h"
#include "list-objects.h"
#include "run-command.h"
+#include "connect.h"
#include "sigchain.h"
#include "version.h"
#include "string-list.h"
return sz;
}
-static FILE *pack_pipe = NULL;
-static void show_commit(struct commit *commit, void *data)
-{
- if (commit->object.flags & BOUNDARY)
- fputc('-', pack_pipe);
- if (fputs(sha1_to_hex(commit->object.sha1), pack_pipe) < 0)
- die("broken output pipe");
- fputc('\n', pack_pipe);
- fflush(pack_pipe);
- free(commit->buffer);
- commit->buffer = NULL;
-}
-
-static void show_object(struct object *obj,
- const struct name_path *path, const char *component,
- void *cb_data)
-{
- show_object_with_name(pack_pipe, obj, path, component);
-}
-
-static void show_edge(struct commit *commit)
-{
- fprintf(pack_pipe, "-%s\n", sha1_to_hex(commit->object.sha1));
-}
-
-static int do_rev_list(int in, int out, void *user_data)
-{
- int i;
- struct rev_info revs;
-
- pack_pipe = xfdopen(out, "w");
- init_revisions(&revs, NULL);
- revs.tag_objects = 1;
- revs.tree_objects = 1;
- revs.blob_objects = 1;
- if (use_thin_pack)
- revs.edge_hint = 1;
-
- for (i = 0; i < want_obj.nr; i++) {
- struct object *o = want_obj.objects[i].item;
- /* why??? */
- o->flags &= ~UNINTERESTING;
- add_pending_object(&revs, o, NULL);
- }
- for (i = 0; i < have_obj.nr; i++) {
- struct object *o = have_obj.objects[i].item;
- o->flags |= UNINTERESTING;
- add_pending_object(&revs, o, NULL);
- }
- setup_revisions(0, NULL, &revs, NULL);
- if (prepare_revision_walk(&revs))
- die("revision walk setup failed");
- mark_edges_uninteresting(revs.commits, &revs, show_edge);
- if (use_thin_pack)
- for (i = 0; i < extra_edge_obj.nr; i++)
- fprintf(pack_pipe, "-%s\n", sha1_to_hex(
- extra_edge_obj.objects[i].item->sha1));
- traverse_commit_list(&revs, show_commit, show_object, NULL);
- fflush(pack_pipe);
- fclose(pack_pipe);
- return 0;
-}
-
static void create_pack_file(void)
{
- struct async rev_list;
struct child_process pack_objects;
char data[8193], progress[128];
char abort_msg[] = "aborting due to possible repository "
"corruption on the remote side.";
int buffered = -1;
ssize_t sz;
- const char *argv[10];
- int arg = 0;
+ const char *argv[12];
+ int i, arg = 0;
+ FILE *pipe_fd;
+ char *shallow_file = NULL;
- argv[arg++] = "pack-objects";
- if (!shallow_nr) {
- argv[arg++] = "--revs";
- if (use_thin_pack)
- argv[arg++] = "--thin";
+ if (shallow_nr) {
+ shallow_file = setup_temporary_shallow();
+ argv[arg++] = "--shallow-file";
+ argv[arg++] = shallow_file;
}
+ argv[arg++] = "pack-objects";
+ argv[arg++] = "--revs";
+ if (use_thin_pack)
+ argv[arg++] = "--thin";
argv[arg++] = "--stdout";
if (!no_progress)
if (start_command(&pack_objects))
die("git upload-pack: unable to fork git-pack-objects");
- if (shallow_nr) {
- memset(&rev_list, 0, sizeof(rev_list));
- rev_list.proc = do_rev_list;
- rev_list.out = pack_objects.in;
- if (start_async(&rev_list))
- die("git upload-pack: unable to fork git-rev-list");
- }
- else {
- FILE *pipe_fd = xfdopen(pack_objects.in, "w");
- int i;
-
- for (i = 0; i < want_obj.nr; i++)
- fprintf(pipe_fd, "%s\n",
- sha1_to_hex(want_obj.objects[i].item->sha1));
- fprintf(pipe_fd, "--not\n");
- for (i = 0; i < have_obj.nr; i++)
- fprintf(pipe_fd, "%s\n",
- sha1_to_hex(have_obj.objects[i].item->sha1));
- fprintf(pipe_fd, "\n");
- fflush(pipe_fd);
- fclose(pipe_fd);
- }
-
+ pipe_fd = xfdopen(pack_objects.in, "w");
+
+ for (i = 0; i < want_obj.nr; i++)
+ fprintf(pipe_fd, "%s\n",
+ sha1_to_hex(want_obj.objects[i].item->sha1));
+ fprintf(pipe_fd, "--not\n");
+ for (i = 0; i < have_obj.nr; i++)
+ fprintf(pipe_fd, "%s\n",
+ sha1_to_hex(have_obj.objects[i].item->sha1));
+ for (i = 0; i < extra_edge_obj.nr; i++)
+ fprintf(pipe_fd, "%s\n",
+ sha1_to_hex(extra_edge_obj.objects[i].item->sha1));
+ fprintf(pipe_fd, "\n");
+ fflush(pipe_fd);
+ fclose(pipe_fd);
/* We read from pack_objects.err to capture stderr output for
* progress bar, and pack_objects.out to capture the pack data.
error("git upload-pack: git-pack-objects died with error.");
goto fail;
}
- if (shallow_nr && finish_async(&rev_list))
- goto fail; /* error was already reported */
+ if (shallow_file) {
+ if (*shallow_file)
+ unlink(shallow_file);
+ free(shallow_file);
+ }
/* flush the data */
if (0 <= buffered) {
--- /dev/null
+#include "cache.h"
+#include "urlmatch.h"
+
+#define URL_ALPHA "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
+#define URL_DIGIT "0123456789"
+#define URL_ALPHADIGIT URL_ALPHA URL_DIGIT
+#define URL_SCHEME_CHARS URL_ALPHADIGIT "+.-"
+#define URL_HOST_CHARS URL_ALPHADIGIT ".-[:]" /* IPv6 literals need [:] */
+#define URL_UNSAFE_CHARS " <>\"%{}|\\^`" /* plus 0x00-0x1F,0x7F-0xFF */
+#define URL_GEN_RESERVED ":/?#[]@"
+#define URL_SUB_RESERVED "!$&'()*+,;="
+#define URL_RESERVED URL_GEN_RESERVED URL_SUB_RESERVED /* only allowed delims */
+
+static int append_normalized_escapes(struct strbuf *buf,
+ const char *from,
+ size_t from_len,
+ const char *esc_extra,
+ const char *esc_ok)
+{
+ /*
+ * Append to strbuf 'buf' characters from string 'from' with length
+ * 'from_len' while unescaping characters that do not need to be escaped
+ * and escaping characters that do. The set of characters to escape
+ * (the complement of which is unescaped) starts out as the RFC 3986
+ * unsafe characters (0x00-0x1F,0x7F-0xFF," <>\"#%{}|\\^`"). If
+ * 'esc_extra' is not NULL, those additional characters will also always
+ * be escaped. If 'esc_ok' is not NULL, those characters will be left
+ * escaped if found that way, but will not be unescaped otherwise (used
+ * for delimiters). If a %-escape sequence is encountered that is not
+ * followed by 2 hexadecimal digits, the sequence is invalid and
+ * false (0) will be returned. Otherwise true (1) will be returned for
+ * success.
+ *
+ * Note that all %-escape sequences will be normalized to UPPERCASE
+ * as indicated in RFC 3986. Unless included in esc_extra or esc_ok
+ * alphanumerics and "-._~" will always be unescaped as per RFC 3986.
+ */
+
+ while (from_len) {
+ int ch = *from++;
+ int was_esc = 0;
+
+ from_len--;
+ if (ch == '%') {
+ if (from_len < 2 ||
+ !isxdigit((unsigned char)from[0]) ||
+ !isxdigit((unsigned char)from[1]))
+ return 0;
+ ch = hexval_table[(unsigned char)*from++] << 4;
+ ch |= hexval_table[(unsigned char)*from++];
+ from_len -= 2;
+ was_esc = 1;
+ }
+ if ((unsigned char)ch <= 0x1F || (unsigned char)ch >= 0x7F ||
+ strchr(URL_UNSAFE_CHARS, ch) ||
+ (esc_extra && strchr(esc_extra, ch)) ||
+ (was_esc && strchr(esc_ok, ch)))
+ strbuf_addf(buf, "%%%02X", (unsigned char)ch);
+ else
+ strbuf_addch(buf, ch);
+ }
+
+ return 1;
+}
+
+char *url_normalize(const char *url, struct url_info *out_info)
+{
+ /*
+ * Normalize NUL-terminated url using the following rules:
+ *
+ * 1. Case-insensitive parts of url will be converted to lower case
+ * 2. %-encoded characters that do not need to be will be unencoded
+ * 3. Characters that are not %-encoded and must be will be encoded
+ * 4. All %-encodings will be converted to upper case hexadecimal
+ * 5. Leading 0s are removed from port numbers
+ * 6. If the default port for the scheme is given it will be removed
+ * 7. A path part (including empty) not starting with '/' has one added
+ * 8. Any dot segments (. or ..) in the path are resolved and removed
+ * 9. IPv6 host literals are allowed (but not normalized or validated)
+ *
+ * The rules are based on information in RFC 3986.
+ *
+ * Please note this function requires a full URL including a scheme
+ * and host part (except for file: URLs which may have an empty host).
+ *
+ * The return value is a newly allocated string that must be freed
+ * or NULL if the url is not valid.
+ *
+ * If out_info is non-NULL, the url and err fields therein will always
+ * be set. If a non-NULL value is returned, it will be stored in
+ * out_info->url as well, out_info->err will be set to NULL and the
+ * other fields of *out_info will also be filled in. If a NULL value
+ * is returned, NULL will be stored in out_info->url and out_info->err
+ * will be set to a brief, translated, error message, but no other
+ * fields will be filled in.
+ *
+ * This is NOT a URL validation function. Full URL validation is NOT
+ * performed. Some invalid host names are passed through this function
+ * undetected. However, most all other problems that make a URL invalid
+ * will be detected (including a missing host for non file: URLs).
+ */
+
+ size_t url_len = strlen(url);
+ struct strbuf norm;
+ size_t spanned;
+ size_t scheme_len, user_off=0, user_len=0, passwd_off=0, passwd_len=0;
+ size_t host_off=0, host_len=0, port_len=0, path_off, path_len, result_len;
+ const char *slash_ptr, *at_ptr, *colon_ptr, *path_start;
+ char *result;
+
+ /*
+ * Copy lowercased scheme and :// suffix, %-escapes are not allowed
+ * First character of scheme must be URL_ALPHA
+ */
+ spanned = strspn(url, URL_SCHEME_CHARS);
+ if (!spanned || !isalpha(url[0]) || spanned + 3 > url_len ||
+ url[spanned] != ':' || url[spanned+1] != '/' || url[spanned+2] != '/') {
+ if (out_info) {
+ out_info->url = NULL;
+ out_info->err = _("invalid URL scheme name or missing '://' suffix");
+ }
+ return NULL; /* Bad scheme and/or missing "://" part */
+ }
+ strbuf_init(&norm, url_len);
+ scheme_len = spanned;
+ spanned += 3;
+ url_len -= spanned;
+ while (spanned--)
+ strbuf_addch(&norm, tolower(*url++));
+
+
+ /*
+ * Copy any username:password if present normalizing %-escapes
+ */
+ at_ptr = strchr(url, '@');
+ slash_ptr = url + strcspn(url, "/?#");
+ if (at_ptr && at_ptr < slash_ptr) {
+ user_off = norm.len;
+ if (at_ptr > url) {
+ if (!append_normalized_escapes(&norm, url, at_ptr - url,
+ "", URL_RESERVED)) {
+ if (out_info) {
+ out_info->url = NULL;
+ out_info->err = _("invalid %XX escape sequence");
+ }
+ strbuf_release(&norm);
+ return NULL;
+ }
+ colon_ptr = strchr(norm.buf + scheme_len + 3, ':');
+ if (colon_ptr) {
+ passwd_off = (colon_ptr + 1) - norm.buf;
+ passwd_len = norm.len - passwd_off;
+ user_len = (passwd_off - 1) - (scheme_len + 3);
+ } else {
+ user_len = norm.len - (scheme_len + 3);
+ }
+ }
+ strbuf_addch(&norm, '@');
+ url_len -= (++at_ptr - url);
+ url = at_ptr;
+ }
+
+
+ /*
+ * Copy the host part excluding any port part, no %-escapes allowed
+ */
+ if (!url_len || strchr(":/?#", *url)) {
+ /* Missing host invalid for all URL schemes except file */
+ if (strncmp(norm.buf, "file:", 5)) {
+ if (out_info) {
+ out_info->url = NULL;
+ out_info->err = _("missing host and scheme is not 'file:'");
+ }
+ strbuf_release(&norm);
+ return NULL;
+ }
+ } else {
+ host_off = norm.len;
+ }
+ colon_ptr = slash_ptr - 1;
+ while (colon_ptr > url && *colon_ptr != ':' && *colon_ptr != ']')
+ colon_ptr--;
+ if (*colon_ptr != ':') {
+ colon_ptr = slash_ptr;
+ } else if (!host_off && colon_ptr < slash_ptr && colon_ptr + 1 != slash_ptr) {
+ /* file: URLs may not have a port number */
+ if (out_info) {
+ out_info->url = NULL;
+ out_info->err = _("a 'file:' URL may not have a port number");
+ }
+ strbuf_release(&norm);
+ return NULL;
+ }
+ spanned = strspn(url, URL_HOST_CHARS);
+ if (spanned < colon_ptr - url) {
+ /* Host name has invalid characters */
+ if (out_info) {
+ out_info->url = NULL;
+ out_info->err = _("invalid characters in host name");
+ }
+ strbuf_release(&norm);
+ return NULL;
+ }
+ while (url < colon_ptr) {
+ strbuf_addch(&norm, tolower(*url++));
+ url_len--;
+ }
+
+
+ /*
+ * Check the port part and copy if not the default (after removing any
+ * leading 0s); no %-escapes allowed
+ */
+ if (colon_ptr < slash_ptr) {
+ /* skip the ':' and leading 0s but not the last one if all 0s */
+ url++;
+ url += strspn(url, "0");
+ if (url == slash_ptr && url[-1] == '0')
+ url--;
+ if (url == slash_ptr) {
+ /* Skip ":" port with no number, it's same as default */
+ } else if (slash_ptr - url == 2 &&
+ !strncmp(norm.buf, "http:", 5) &&
+ !strncmp(url, "80", 2)) {
+ /* Skip http :80 as it's the default */
+ } else if (slash_ptr - url == 3 &&
+ !strncmp(norm.buf, "https:", 6) &&
+ !strncmp(url, "443", 3)) {
+ /* Skip https :443 as it's the default */
+ } else {
+ /*
+ * Port number must be all digits with leading 0s removed
+ * and since all the protocols we deal with have a 16-bit
+ * port number it must also be in the range 1..65535
+ * 0 is not allowed because that means "next available"
+ * on just about every system and therefore cannot be used
+ */
+ unsigned long pnum = 0;
+ spanned = strspn(url, URL_DIGIT);
+ if (spanned < slash_ptr - url) {
+ /* port number has invalid characters */
+ if (out_info) {
+ out_info->url = NULL;
+ out_info->err = _("invalid port number");
+ }
+ strbuf_release(&norm);
+ return NULL;
+ }
+ if (slash_ptr - url <= 5)
+ pnum = strtoul(url, NULL, 10);
+ if (pnum == 0 || pnum > 65535) {
+ /* port number not in range 1..65535 */
+ if (out_info) {
+ out_info->url = NULL;
+ out_info->err = _("invalid port number");
+ }
+ strbuf_release(&norm);
+ return NULL;
+ }
+ strbuf_addch(&norm, ':');
+ strbuf_add(&norm, url, slash_ptr - url);
+ port_len = slash_ptr - url;
+ }
+ url_len -= slash_ptr - colon_ptr;
+ url = slash_ptr;
+ }
+ if (host_off)
+ host_len = norm.len - host_off;
+
+
+ /*
+ * Now copy the path resolving any . and .. segments being careful not
+ * to corrupt the URL by unescaping any delimiters, but do add an
+ * initial '/' if it's missing and do normalize any %-escape sequences.
+ */
+ path_off = norm.len;
+ path_start = norm.buf + path_off;
+ strbuf_addch(&norm, '/');
+ if (*url == '/') {
+ url++;
+ url_len--;
+ }
+ for (;;) {
+ const char *seg_start;
+ size_t seg_start_off = norm.len;
+ const char *next_slash = url + strcspn(url, "/?#");
+ int skip_add_slash = 0;
+
+ /*
+ * RFC 3689 indicates that any . or .. segments should be
+ * unescaped before being checked for.
+ */
+ if (!append_normalized_escapes(&norm, url, next_slash - url, "",
+ URL_RESERVED)) {
+ if (out_info) {
+ out_info->url = NULL;
+ out_info->err = _("invalid %XX escape sequence");
+ }
+ strbuf_release(&norm);
+ return NULL;
+ }
+
+ seg_start = norm.buf + seg_start_off;
+ if (!strcmp(seg_start, ".")) {
+ /* ignore a . segment; be careful not to remove initial '/' */
+ if (seg_start == path_start + 1) {
+ strbuf_setlen(&norm, norm.len - 1);
+ skip_add_slash = 1;
+ } else {
+ strbuf_setlen(&norm, norm.len - 2);
+ }
+ } else if (!strcmp(seg_start, "..")) {
+ /*
+ * ignore a .. segment and remove the previous segment;
+ * be careful not to remove initial '/' from path
+ */
+ const char *prev_slash = norm.buf + norm.len - 3;
+ if (prev_slash == path_start) {
+ /* invalid .. because no previous segment to remove */
+ if (out_info) {
+ out_info->url = NULL;
+ out_info->err = _("invalid '..' path segment");
+ }
+ strbuf_release(&norm);
+ return NULL;
+ }
+ while (*--prev_slash != '/') {}
+ if (prev_slash == path_start) {
+ strbuf_setlen(&norm, prev_slash - norm.buf + 1);
+ skip_add_slash = 1;
+ } else {
+ strbuf_setlen(&norm, prev_slash - norm.buf);
+ }
+ }
+ url_len -= next_slash - url;
+ url = next_slash;
+ /* if the next char is not '/' done with the path */
+ if (*url != '/')
+ break;
+ url++;
+ url_len--;
+ if (!skip_add_slash)
+ strbuf_addch(&norm, '/');
+ }
+ path_len = norm.len - path_off;
+
+
+ /*
+ * Now simply copy the rest, if any, only normalizing %-escapes and
+ * being careful not to corrupt the URL by unescaping any delimiters.
+ */
+ if (*url) {
+ if (!append_normalized_escapes(&norm, url, url_len, "", URL_RESERVED)) {
+ if (out_info) {
+ out_info->url = NULL;
+ out_info->err = _("invalid %XX escape sequence");
+ }
+ strbuf_release(&norm);
+ return NULL;
+ }
+ }
+
+
+ result = strbuf_detach(&norm, &result_len);
+ if (out_info) {
+ out_info->url = result;
+ out_info->err = NULL;
+ out_info->url_len = result_len;
+ out_info->scheme_len = scheme_len;
+ out_info->user_off = user_off;
+ out_info->user_len = user_len;
+ out_info->passwd_off = passwd_off;
+ out_info->passwd_len = passwd_len;
+ out_info->host_off = host_off;
+ out_info->host_len = host_len;
+ out_info->port_len = port_len;
+ out_info->path_off = path_off;
+ out_info->path_len = path_len;
+ }
+ return result;
+}
+
+static size_t url_match_prefix(const char *url,
+ const char *url_prefix,
+ size_t url_prefix_len)
+{
+ /*
+ * url_prefix matches url if url_prefix is an exact match for url or it
+ * is a prefix of url and the match ends on a path component boundary.
+ * Both url and url_prefix are considered to have an implicit '/' on the
+ * end for matching purposes if they do not already.
+ *
+ * url must be NUL terminated. url_prefix_len is the length of
+ * url_prefix which need not be NUL terminated.
+ *
+ * The return value is the length of the match in characters (including
+ * the final '/' even if it's implicit) or 0 for no match.
+ *
+ * Passing NULL as url and/or url_prefix will always cause 0 to be
+ * returned without causing any faults.
+ */
+ if (!url || !url_prefix)
+ return 0;
+ if (!url_prefix_len || (url_prefix_len == 1 && *url_prefix == '/'))
+ return (!*url || *url == '/') ? 1 : 0;
+ if (url_prefix[url_prefix_len - 1] == '/')
+ url_prefix_len--;
+ if (strncmp(url, url_prefix, url_prefix_len))
+ return 0;
+ if ((strlen(url) == url_prefix_len) || (url[url_prefix_len] == '/'))
+ return url_prefix_len + 1;
+ return 0;
+}
+
+int match_urls(const struct url_info *url,
+ const struct url_info *url_prefix,
+ int *exactusermatch)
+{
+ /*
+ * url_prefix matches url if the scheme, host and port of url_prefix
+ * are the same as those of url and the path portion of url_prefix
+ * is the same as the path portion of url or it is a prefix that
+ * matches at a '/' boundary. If url_prefix contains a user name,
+ * that must also exactly match the user name in url.
+ *
+ * If the user, host, port and path match in this fashion, the returned
+ * value is the length of the path match including any implicit
+ * final '/'. For example, "http://me@example.com/path" is matched by
+ * "http://example.com" with a path length of 1.
+ *
+ * If there is a match and exactusermatch is not NULL, then
+ * *exactusermatch will be set to true if both url and url_prefix
+ * contained a user name or false if url_prefix did not have a
+ * user name. If there is no match *exactusermatch is left untouched.
+ */
+ int usermatched = 0;
+ int pathmatchlen;
+
+ if (!url || !url_prefix || !url->url || !url_prefix->url)
+ return 0;
+
+ /* check the scheme */
+ if (url_prefix->scheme_len != url->scheme_len ||
+ strncmp(url->url, url_prefix->url, url->scheme_len))
+ return 0; /* schemes do not match */
+
+ /* check the user name if url_prefix has one */
+ if (url_prefix->user_off) {
+ if (!url->user_off || url->user_len != url_prefix->user_len ||
+ strncmp(url->url + url->user_off,
+ url_prefix->url + url_prefix->user_off,
+ url->user_len))
+ return 0; /* url_prefix has a user but it's not a match */
+ usermatched = 1;
+ }
+
+ /* check the host and port */
+ if (url_prefix->host_len != url->host_len ||
+ strncmp(url->url + url->host_off,
+ url_prefix->url + url_prefix->host_off, url->host_len))
+ return 0; /* host names and/or ports do not match */
+
+ /* check the path */
+ pathmatchlen = url_match_prefix(
+ url->url + url->path_off,
+ url_prefix->url + url_prefix->path_off,
+ url_prefix->url_len - url_prefix->path_off);
+
+ if (pathmatchlen && exactusermatch)
+ *exactusermatch = usermatched;
+ return pathmatchlen;
+}
+
+int urlmatch_config_entry(const char *var, const char *value, void *cb)
+{
+ struct string_list_item *item;
+ struct urlmatch_config *collect = cb;
+ struct urlmatch_item *matched;
+ struct url_info *url = &collect->url;
+ const char *key, *dot;
+ struct strbuf synthkey = STRBUF_INIT;
+ size_t matched_len = 0;
+ int user_matched = 0;
+ int retval;
+
+ key = skip_prefix(var, collect->section);
+ if (!key || *(key++) != '.') {
+ if (collect->cascade_fn)
+ return collect->cascade_fn(var, value, cb);
+ return 0; /* not interested */
+ }
+ dot = strrchr(key, '.');
+ if (dot) {
+ char *config_url, *norm_url;
+ struct url_info norm_info;
+
+ config_url = xmemdupz(key, dot - key);
+ norm_url = url_normalize(config_url, &norm_info);
+ free(config_url);
+ if (!norm_url)
+ return 0;
+ matched_len = match_urls(url, &norm_info, &user_matched);
+ free(norm_url);
+ if (!matched_len)
+ return 0;
+ key = dot + 1;
+ }
+
+ if (collect->key && strcmp(key, collect->key))
+ return 0;
+
+ item = string_list_insert(&collect->vars, key);
+ if (!item->util) {
+ matched = xcalloc(1, sizeof(*matched));
+ item->util = matched;
+ } else {
+ matched = item->util;
+ /*
+ * Is our match shorter? Is our match the same
+ * length, and without user while the current
+ * candidate is with user? Then we cannot use it.
+ */
+ if (matched_len < matched->matched_len ||
+ ((matched_len == matched->matched_len) &&
+ (!user_matched && matched->user_matched)))
+ return 0;
+ /* Otherwise, replace it with this one. */
+ }
+
+ matched->matched_len = matched_len;
+ matched->user_matched = user_matched;
+ strbuf_addstr(&synthkey, collect->section);
+ strbuf_addch(&synthkey, '.');
+ strbuf_addstr(&synthkey, key);
+ retval = collect->collect_fn(synthkey.buf, value, collect->cb);
+
+ strbuf_release(&synthkey);
+ return retval;
+}
--- /dev/null
+#ifndef URL_MATCH_H
+#include "string-list.h"
+
+struct url_info {
+ /* normalized url on success, must be freed, otherwise NULL */
+ char *url;
+ /* if !url, a brief reason for the failure, otherwise NULL */
+ const char *err;
+
+ /* the rest of the fields are only set if url != NULL */
+
+ size_t url_len; /* total length of url (which is now normalized) */
+ size_t scheme_len; /* length of scheme name (excluding final :) */
+ size_t user_off; /* offset into url to start of user name (0 => none) */
+ size_t user_len; /* length of user name; if user_off != 0 but
+ user_len == 0, an empty user name was given */
+ size_t passwd_off; /* offset into url to start of passwd (0 => none) */
+ size_t passwd_len; /* length of passwd; if passwd_off != 0 but
+ passwd_len == 0, an empty passwd was given */
+ size_t host_off; /* offset into url to start of host name (0 => none) */
+ size_t host_len; /* length of host name; this INCLUDES any ':portnum';
+ * file urls may have host_len == 0 */
+ size_t port_len; /* if a portnum is present (port_len != 0), it has
+ * this length (excluding the leading ':') at the
+ * end of the host name (always 0 for file urls) */
+ size_t path_off; /* offset into url to the start of the url path;
+ * this will always point to a '/' character
+ * after the url has been normalized */
+ size_t path_len; /* length of path portion excluding any trailing
+ * '?...' and '#...' portion; will always be >= 1 */
+};
+
+extern char *url_normalize(const char *, struct url_info *);
+extern int match_urls(const struct url_info *url, const struct url_info *url_prefix, int *exactusermatch);
+
+struct urlmatch_item {
+ size_t matched_len;
+ char user_matched;
+};
+
+struct urlmatch_config {
+ struct string_list vars;
+ struct url_info url;
+ const char *section;
+ const char *key;
+
+ void *cb;
+ int (*collect_fn)(const char *var, const char *value, void *cb);
+ int (*cascade_fn)(const char *var, const char *value, void *cb);
+};
+
+extern int urlmatch_config_entry(const char *var, const char *value, void *cb);
+
+#endif /* URL_MATCH_H */
if (!obj || process(walker, obj))
return -1;
}
- free(tree->buffer);
- tree->buffer = NULL;
- tree->size = 0;
- tree->object.parsed = 0;
+ free_tree_buffer(tree);
return 0;
}
return ret;
}
+/*
+ * Limit size of IO chunks, because huge chunks only cause pain. OS X
+ * 64-bit is buggy, returning EINVAL if len >= INT_MAX; and even in
+ * the absense of bugs, large chunks can result in bad latencies when
+ * you decide to kill the process.
+ */
+#define MAX_IO_SIZE (8*1024*1024)
+
/*
* xread() is the same a read(), but it automatically restarts read()
* operations with a recoverable error (EAGAIN and EINTR). xread()
ssize_t xread(int fd, void *buf, size_t len)
{
ssize_t nr;
+ if (len > MAX_IO_SIZE)
+ len = MAX_IO_SIZE;
while (1) {
nr = read(fd, buf, len);
if ((nr < 0) && (errno == EAGAIN || errno == EINTR))
ssize_t xwrite(int fd, const void *buf, size_t len)
{
ssize_t nr;
+ if (len > MAX_IO_SIZE)
+ len = MAX_IO_SIZE;
while (1) {
nr = write(fd, buf, len);
if ((nr < 0) && (errno == EAGAIN || errno == EINTR))
#include "cache.h"
+#include "pathspec.h"
#include "wt-status.h"
#include "object.h"
#include "dir.h"
#include "diffcore.h"
#include "quote.h"
#include "run-command.h"
+#include "argv-array.h"
#include "remote.h"
#include "refs.h"
#include "submodule.h"
strbuf_vaddf(&sb, fmt, ap);
if (!sb.len) {
- strbuf_addch(&sb, comment_line_char);
- if (!trail)
- strbuf_addch(&sb, ' ');
+ if (s->display_comment_prefix) {
+ strbuf_addch(&sb, comment_line_char);
+ if (!trail)
+ strbuf_addch(&sb, ' ');
+ }
color_print_strbuf(s->fp, color, &sb);
if (trail)
fprintf(s->fp, "%s", trail);
eol = strchr(line, '\n');
strbuf_reset(&linebuf);
- if (at_bol) {
+ if (at_bol && s->display_comment_prefix) {
strbuf_addch(&linebuf, comment_line_char);
if (*line != '\n' && *line != '\t')
strbuf_addch(&linebuf, ' ');
s->untracked.strdup_strings = 1;
s->ignored.strdup_strings = 1;
s->show_branch = -1; /* unspecified */
+ s->display_comment_prefix = 0;
}
static void wt_status_print_unmerged_header(struct wt_status *s)
}
}
- if (!advice_status_hints)
+ if (!s->hints)
return;
if (s->whence != FROM_COMMIT)
;
const char *c = color(WT_STATUS_HEADER, s);
status_printf_ln(s, c, _("Changes to be committed:"));
- if (!advice_status_hints)
+ if (!s->hints)
return;
if (s->whence != FROM_COMMIT)
; /* NEEDSWORK: use "git reset --unresolve"??? */
const char *c = color(WT_STATUS_HEADER, s);
status_printf_ln(s, c, _("Changes not staged for commit:"));
- if (!advice_status_hints)
+ if (!s->hints)
return;
if (!has_deleted)
status_printf_ln(s, c, _(" (use \"git add <file>...\" to update what will be committed)"));
{
const char *c = color(WT_STATUS_HEADER, s);
status_printf_ln(s, c, "%s:", what);
- if (!advice_status_hints)
+ if (!s->hints)
return;
status_printf_ln(s, c, _(" (use \"git %s <file>...\" to include in what will be committed)"), how);
status_printf_ln(s, c, "");
}
rev.diffopt.format_callback = wt_status_collect_changed_cb;
rev.diffopt.format_callback_data = s;
- init_pathspec(&rev.prune_data, s->pathspec);
+ copy_pathspec(&rev.prune_data, &s->pathspec);
run_diff_files(&rev, 0);
}
rev.diffopt.detect_rename = 1;
rev.diffopt.rename_limit = 200;
rev.diffopt.break_opt = 0;
- init_pathspec(&rev.prune_data, s->pathspec);
+ copy_pathspec(&rev.prune_data, &s->pathspec);
run_diff_index(&rev, 1);
}
static void wt_status_collect_changes_initial(struct wt_status *s)
{
- struct pathspec pathspec;
int i;
- init_pathspec(&pathspec, s->pathspec);
for (i = 0; i < active_nr; i++) {
struct string_list_item *it;
struct wt_status_change_data *d;
const struct cache_entry *ce = active_cache[i];
- if (!ce_path_match(ce, &pathspec))
+ if (!ce_path_match(ce, &s->pathspec))
continue;
it = string_list_insert(&s->change, ce->name);
d = it->util;
else
d->index_status = DIFF_STATUS_ADDED;
}
- free_pathspec(&pathspec);
}
static void wt_status_collect_untracked(struct wt_status *s)
dir.flags |= DIR_SHOW_IGNORED_TOO;
setup_standard_excludes(&dir);
- fill_directory(&dir, s->pathspec);
+ fill_directory(&dir, &s->pathspec);
for (i = 0; i < dir.nr; i++) {
struct dir_entry *ent = dir.entries[i];
if (cache_name_is_other(ent->name, ent->len) &&
- match_pathspec(s->pathspec, ent->name, ent->len, 0, NULL))
+ match_pathspec_depth(&s->pathspec, ent->name, ent->len, 0, NULL))
string_list_insert(&s->untracked, ent->name);
free(ent);
}
for (i = 0; i < dir.ignored_nr; i++) {
struct dir_entry *ent = dir.ignored[i];
if (cache_name_is_other(ent->name, ent->len) &&
- match_pathspec(s->pathspec, ent->name, ent->len, 0, NULL))
+ match_pathspec_depth(&s->pathspec, ent->name, ent->len, 0, NULL))
string_list_insert(&s->ignored, ent->name);
free(ent);
}
char summary_limit[64];
char index[PATH_MAX];
const char *env[] = { NULL, NULL };
- const char *argv[8];
-
- env[0] = index;
- argv[0] = "submodule";
- argv[1] = "summary";
- argv[2] = uncommitted ? "--files" : "--cached";
- argv[3] = "--for-status";
- argv[4] = "--summary-limit";
- argv[5] = summary_limit;
- argv[6] = uncommitted ? NULL : (s->amend ? "HEAD^" : "HEAD");
- argv[7] = NULL;
+ struct argv_array argv = ARGV_ARRAY_INIT;
+ struct strbuf cmd_stdout = STRBUF_INIT;
+ struct strbuf summary = STRBUF_INIT;
+ char *summary_content;
+ size_t len;
sprintf(summary_limit, "%d", s->submodule_summary);
snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", s->index_file);
+ env[0] = index;
+ argv_array_push(&argv, "submodule");
+ argv_array_push(&argv, "summary");
+ argv_array_push(&argv, uncommitted ? "--files" : "--cached");
+ argv_array_push(&argv, "--for-status");
+ argv_array_push(&argv, "--summary-limit");
+ argv_array_push(&argv, summary_limit);
+ if (!uncommitted)
+ argv_array_push(&argv, s->amend ? "HEAD^" : "HEAD");
+
memset(&sm_summary, 0, sizeof(sm_summary));
- sm_summary.argv = argv;
+ sm_summary.argv = argv.argv;
sm_summary.env = env;
sm_summary.git_cmd = 1;
sm_summary.no_stdin = 1;
fflush(s->fp);
- sm_summary.out = dup(fileno(s->fp)); /* run_command closes it */
+ sm_summary.out = -1;
+
run_command(&sm_summary);
+ argv_array_clear(&argv);
+
+ len = strbuf_read(&cmd_stdout, sm_summary.out, 1024);
+
+ /* prepend header, only if there's an actual output */
+ if (len) {
+ if (uncommitted)
+ strbuf_addstr(&summary, _("Submodules changed but not updated:"));
+ else
+ strbuf_addstr(&summary, _("Submodule changes to be committed:"));
+ strbuf_addstr(&summary, "\n\n");
+ }
+ strbuf_addbuf(&summary, &cmd_stdout);
+ strbuf_release(&cmd_stdout);
+
+ if (s->display_comment_prefix) {
+ summary_content = strbuf_detach(&summary, &len);
+ strbuf_add_commented_lines(&summary, summary_content, len);
+ free(summary_content);
+ }
+
+ fputs(summary.buf, s->fp);
+ strbuf_release(&summary);
}
static void wt_status_print_other(struct wt_status *s,
strbuf_release(&buf);
if (!column_active(s->colopts))
- return;
+ goto conclude;
- strbuf_addf(&buf, "%s#\t%s",
+ strbuf_addf(&buf, "%s%s\t%s",
color(WT_STATUS_HEADER, s),
+ s->display_comment_prefix ? "#" : "",
color(WT_STATUS_UNTRACKED, s));
memset(&copts, 0, sizeof(copts));
copts.padding = 1;
print_columns(&output, s->colopts, &copts);
string_list_clear(&output, 0);
strbuf_release(&buf);
+conclude:
+ status_printf_ln(s, GIT_COLOR_NORMAL, "");
}
static void wt_status_print_verbose(struct wt_status *s)
struct strbuf sb = STRBUF_INIT;
const char *cp, *ep;
struct branch *branch;
+ char comment_line_string[3];
+ int i;
assert(s->branch && !s->is_initial);
if (prefixcmp(s->branch, "refs/heads/"))
if (!format_tracking_info(branch, &sb))
return;
+ i = 0;
+ if (s->display_comment_prefix) {
+ comment_line_string[i++] = comment_line_char;
+ comment_line_string[i++] = ' ';
+ }
+ comment_line_string[i] = '\0';
+
for (cp = sb.buf; (ep = strchr(cp, '\n')) != NULL; cp = ep + 1)
color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s),
- "%c %.*s", comment_line_char,
+ "%s%.*s", comment_line_string,
(int)(ep - cp), cp);
- color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "%c",
- comment_line_char);
+ if (s->display_comment_prefix)
+ color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "%c",
+ comment_line_char);
+ else
+ fprintf_ln(s->fp, "");
}
static int has_unmerged(struct wt_status *s)
{
if (has_unmerged(s)) {
status_printf_ln(s, color, _("You have unmerged paths."));
- if (advice_status_hints)
+ if (s->hints)
status_printf_ln(s, color,
_(" (fix conflicts and run \"git commit\")"));
} else {
status_printf_ln(s, color,
_("All conflicts fixed but you are still merging."));
- if (advice_status_hints)
+ if (s->hints)
status_printf_ln(s, color,
_(" (use \"git commit\" to conclude merge)"));
}
if (state->am_empty_patch)
status_printf_ln(s, color,
_("The current patch is empty."));
- if (advice_status_hints) {
+ if (s->hints) {
if (!state->am_empty_patch)
status_printf_ln(s, color,
_(" (fix conflicts and then run \"git am --continue\")"));
else
status_printf_ln(s, color,
_("You are currently rebasing."));
- if (advice_status_hints) {
+ if (s->hints) {
status_printf_ln(s, color,
_(" (fix conflicts and then run \"git rebase --continue\")"));
status_printf_ln(s, color,
else
status_printf_ln(s, color,
_("You are currently rebasing."));
- if (advice_status_hints)
+ if (s->hints)
status_printf_ln(s, color,
_(" (all conflicts fixed: run \"git rebase --continue\")"));
} else if (split_commit_in_progress(s)) {
else
status_printf_ln(s, color,
_("You are currently splitting a commit during a rebase."));
- if (advice_status_hints)
+ if (s->hints)
status_printf_ln(s, color,
_(" (Once your working directory is clean, run \"git rebase --continue\")"));
} else {
else
status_printf_ln(s, color,
_("You are currently editing a commit during a rebase."));
- if (advice_status_hints && !s->amend) {
+ if (s->hints && !s->amend) {
status_printf_ln(s, color,
_(" (use \"git commit --amend\" to amend the current commit)"));
status_printf_ln(s, color,
const char *color)
{
status_printf_ln(s, color, _("You are currently cherry-picking."));
- if (advice_status_hints) {
+ if (s->hints) {
if (has_unmerged(s))
status_printf_ln(s, color,
_(" (fix conflicts and run \"git cherry-pick --continue\")"));
{
status_printf_ln(s, color, _("You are currently reverting commit %s."),
find_unique_abbrev(state->revert_head_sha1, DEFAULT_ABBREV));
- if (advice_status_hints) {
+ if (s->hints) {
if (has_unmerged(s))
status_printf_ln(s, color,
_(" (fix conflicts and run \"git revert --continue\")"));
else
status_printf_ln(s, color,
_("You are currently bisecting."));
- if (advice_status_hints)
+ if (s->hints)
status_printf_ln(s, color,
_(" (use \"git bisect reset\" to get back to the original branch)"));
wt_status_print_trailer(s);
}
} else if (s->commitable)
status_printf_ln(s, GIT_COLOR_NORMAL, _("Untracked files not listed%s"),
- advice_status_hints
+ s->hints
? _(" (use -u option to show untracked files)") : "");
if (s->verbose)
else if (s->nowarn)
; /* nothing */
else if (s->workdir_dirty) {
- if (advice_status_hints)
+ if (s->hints)
printf(_("no changes added to commit "
"(use \"git add\" and/or \"git commit -a\")\n"));
else
printf(_("no changes added to commit\n"));
} else if (s->untracked.nr) {
- if (advice_status_hints)
+ if (s->hints)
printf(_("nothing added to commit but untracked files "
"present (use \"git add\" to track)\n"));
else
printf(_("nothing added to commit but untracked files present\n"));
} else if (s->is_initial) {
- if (advice_status_hints)
+ if (s->hints)
printf(_("nothing to commit (create/copy files "
"and use \"git add\" to track)\n"));
else
printf(_("nothing to commit\n"));
} else if (!s->show_untracked_files) {
- if (advice_status_hints)
+ if (s->hints)
printf(_("nothing to commit (use -u to show untracked files)\n"));
else
printf(_("nothing to commit\n"));
const char *base;
const char *branch_name;
int num_ours, num_theirs;
+ int upstream_is_gone = 0;
color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "## ");
branch = branch_get(s->branch + 11);
if (s->is_initial)
color_fprintf(s->fp, header_color, _("Initial commit on "));
- if (!stat_tracking_info(branch, &num_ours, &num_theirs)) {
- color_fprintf(s->fp, branch_color_local, "%s", branch_name);
+
+ color_fprintf(s->fp, branch_color_local, "%s", branch_name);
+
+ switch (stat_tracking_info(branch, &num_ours, &num_theirs)) {
+ case 0:
+ /* no base */
fputc(s->null_termination ? '\0' : '\n', s->fp);
return;
+ case -1:
+ /* with "gone" base */
+ upstream_is_gone = 1;
+ break;
+ default:
+ /* with base */
+ break;
}
base = branch->merge[0]->dst;
base = shorten_unambiguous_ref(base, 0);
- color_fprintf(s->fp, branch_color_local, "%s", branch_name);
color_fprintf(s->fp, header_color, "...");
color_fprintf(s->fp, branch_color_remote, "%s", base);
+ if (!upstream_is_gone && !num_ours && !num_theirs) {
+ fputc(s->null_termination ? '\0' : '\n', s->fp);
+ return;
+ }
+
color_fprintf(s->fp, header_color, " [");
- if (!num_ours) {
+ if (upstream_is_gone) {
+ color_fprintf(s->fp, header_color, _("gone"));
+ } else if (!num_ours) {
color_fprintf(s->fp, header_color, _("behind "));
color_fprintf(s->fp, branch_color_remote, "%d", num_theirs);
} else if (!num_theirs) {
int is_initial;
char *branch;
const char *reference;
- const char **pathspec;
+ struct pathspec pathspec;
int verbose;
int amend;
enum commit_whence whence;
int nowarn;
int use_color;
+ int display_comment_prefix;
int relative_paths;
int submodule_summary;
int show_ignored_files;
unsigned colopts;
int null_termination;
int show_branch;
+ int hints;
/* These are computed during processing of the individual sections */
int commitable;