mirror of
https://github.com/git/git.git
synced 2026-03-13 10:23:30 +01:00
Merge branch 'master' of git://repo.or.cz/alt-git
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -51,6 +51,7 @@ git-gc
|
||||
git-get-tar-commit-id
|
||||
git-grep
|
||||
git-hash-object
|
||||
git-help
|
||||
git-http-fetch
|
||||
git-http-push
|
||||
git-imap-send
|
||||
|
||||
67
Documentation/RelNotes-1.6.0.2.txt
Normal file
67
Documentation/RelNotes-1.6.0.2.txt
Normal file
@@ -0,0 +1,67 @@
|
||||
GIT v1.6.0.2 Release Notes
|
||||
==========================
|
||||
|
||||
Fixes since v1.6.0.1
|
||||
--------------------
|
||||
|
||||
* Installation on platforms that needs .exe suffix to git-* programs were
|
||||
broken in 1.6.0.1.
|
||||
|
||||
* Installation on filesystems without symbolic links support did nto
|
||||
work well.
|
||||
|
||||
* In-tree documentations and test scripts now use "git foo" form to set a
|
||||
better example, instead of the "git-foo" form (which is an acceptable
|
||||
form if you have "PATH=$(git --exec-path):$PATH" in your script)
|
||||
|
||||
* Many commands did not use the correct working tree location when used
|
||||
with GIT_WORK_TREE environment settings.
|
||||
|
||||
|
||||
* "git apply --unidiff-zero" incorrectly applied a -U0 patch that inserts
|
||||
a new line before the second line.
|
||||
|
||||
* "git blame -c" did not exactly work like "git annotate" when range
|
||||
boundaries are involved.
|
||||
|
||||
* "git clone $there $here/" with extra trailing slashes after explicit
|
||||
local directory name $here did not work as expected.
|
||||
|
||||
* "git diff --dirstat -M" did not add changes in subdirectories up
|
||||
correctly for renamed paths.
|
||||
|
||||
* "git diff --cumulative" did not imply "--dirstat".
|
||||
|
||||
* "git for-each-ref refs/heads/" did not work as expected.
|
||||
|
||||
* "git gui" allowed users to feed patch without any context to be applied.
|
||||
|
||||
* "git gui" botched parsing "diff" output when a line that begins with two
|
||||
dashes and a space gets removed or a line that begins with two pluses
|
||||
and a space gets added.
|
||||
|
||||
* "git gui" translation updates and i18n fixes.
|
||||
|
||||
* "git log -i --grep=pattern" did not ignore case; neither "git log -E
|
||||
--grep=pattern" triggered extended regexp.
|
||||
|
||||
* "git log --pretty="%ad" --date=short" did not use short format when
|
||||
showing the timestamp.
|
||||
|
||||
* Build procedure for "git shell" that used stub versions of some
|
||||
functions and globals was not understood by linkers on some platforms.
|
||||
|
||||
* "git stash" was fooled by a stat-dirty but otherwise unmodified paths
|
||||
and refused to work until the user refreshed the index.
|
||||
|
||||
* "git verify-pack -v" did not work correctly when given more than one
|
||||
packfile.
|
||||
|
||||
Also contains many documentation updates.
|
||||
|
||||
--
|
||||
exec >/var/tmp/1
|
||||
O=v1.6.0.1-61-g1eff26c
|
||||
echo O=$(git describe maint)
|
||||
git shortlog --no-merges $O..maint
|
||||
|
||||
@@ -13,7 +13,8 @@ on.
|
||||
|
||||
(subsystems)
|
||||
|
||||
* ...
|
||||
* gitk can call out to git-gui to view "git blame" output; git-gui in turn
|
||||
can run gitk from its blame view.
|
||||
|
||||
(portability)
|
||||
|
||||
@@ -28,17 +29,60 @@ on.
|
||||
* The underlying diff machinery to produce textual output has been
|
||||
optimized, which would result in faster "git blame" processing.
|
||||
|
||||
* Most of the test scripts (but not the ones that try to run servers)
|
||||
can be run in parallel.
|
||||
|
||||
(usability, bells and whistles)
|
||||
|
||||
* "git checkout --track origin/hack" used to be a syntax error. It now
|
||||
DWIMs to create a corresponding local branch "hack", i.e. acts as if you
|
||||
said "git checkout --track -b hack origin/hack".
|
||||
|
||||
* "git cherry-pick" can also utilize rerere for conflict resolution.
|
||||
|
||||
* "git commit --author=$name" can look up author name from existing
|
||||
commits.
|
||||
|
||||
* "git count-objects" reports the on-disk footprint for packfiles and
|
||||
their corresponding idx files.
|
||||
|
||||
* "git daemon" learned --max-connections=<count> option.
|
||||
|
||||
* "git diff" learned to mimick --suppress-blank-empty from GNU diff via a
|
||||
configuration option.
|
||||
|
||||
* "git diff" learned to put more sensible hunk headers for Python and
|
||||
HTML contents.
|
||||
|
||||
* "git help" learned to use GIT_MAN_VIEWER environment variable before
|
||||
using "man" program.
|
||||
|
||||
* "git imap-send" can optionally talk SSL.
|
||||
|
||||
* "git index-pack" is more careful against disk corruption while
|
||||
completing a thin pack.
|
||||
|
||||
* "git log --check" and "git log --exit-code" passes their underlying diff
|
||||
status with their exit status code.
|
||||
|
||||
* "git log" learned --simplify-merges, a milder variant of --full-history;
|
||||
"gitk --simplify-merges" is easier to view than with --full-history.
|
||||
|
||||
* "git merge --squash" and "git merge --no-ff" into an unborn branch are
|
||||
noticed as user errors.
|
||||
|
||||
* "git merge -s $strategy" can use a custom built strategy if you have a
|
||||
command "git-merge-$strategy" on your $PATH.
|
||||
|
||||
* "git reflog expire branch" can be used in place of "git reflog expire
|
||||
refs/heads/branch".
|
||||
|
||||
* "git submodule foreach" subcommand allows you to iterate over checked
|
||||
out submodules.
|
||||
|
||||
* "git submodule sync" subcommands allows you to update the origin URL
|
||||
recorded in submodule directories from the toplevel .gitmodules file.
|
||||
|
||||
(internal)
|
||||
|
||||
* "git hash-object" learned to lie about the path being hashed, so that
|
||||
@@ -51,8 +95,26 @@ Fixes since v1.6.0
|
||||
All of the fixes in v1.6.0.X maintenance series are included in this
|
||||
release, unless otherwise noted.
|
||||
|
||||
* "git add" and "git update-index" incorrectly allowed adding S/F when S
|
||||
is a tracked symlink that points at a directory D that has a path F in
|
||||
it (we still need to fix a similar nonsense when S is a submodule and F
|
||||
is a path in it).
|
||||
|
||||
* "git diff --stdin" used to take two trees on a line and compared them,
|
||||
but we droppped support for such a use case long time ago. This has
|
||||
been resurrected.
|
||||
|
||||
* "git filter-branch" failed to rewrite a tag name with slashes in it.
|
||||
|
||||
* "git push --tags --all $there" failed with generic usage message without
|
||||
telling saying these two options are incompatible.
|
||||
|
||||
* "git log --author/--committer" match used to potentially match the
|
||||
timestamp part, exposing internal implementation detail. Also these did
|
||||
not work with --fixed-strings match at all.
|
||||
|
||||
--
|
||||
exec >/var/tmp/1
|
||||
O=v1.6.0-48-ge28a867
|
||||
O=v1.6.0.1-266-gaf9552f
|
||||
echo O=$(git describe master)
|
||||
git shortlog --no-merges $O..master ^maint
|
||||
|
||||
@@ -697,7 +697,7 @@ gitcvs.logfile::
|
||||
Path to a log file where the CVS server interface well... logs
|
||||
various stuff. See linkgit:git-cvsserver[1].
|
||||
|
||||
gitcvs.usecrlfattr
|
||||
gitcvs.usecrlfattr::
|
||||
If true, the server will look up the `crlf` attribute for
|
||||
files to determine the '-k' modes to use. If `crlf` is set,
|
||||
the '-k' mode will be left blank, so cvs clients will
|
||||
|
||||
@@ -59,12 +59,11 @@ endif::git-format-patch[]
|
||||
lines.
|
||||
|
||||
--dirstat[=limit]::
|
||||
Output only the sub-directories that are impacted by a diff,
|
||||
and to what degree they are impacted. You can override the
|
||||
default cut-off in percent (3) by "--dirstat=limit". If you
|
||||
want to enable "cumulative" directory statistics, you can use
|
||||
the "--cumulative" flag, which adds up percentages recursively
|
||||
even when they have been already reported for a sub-directory.
|
||||
Output the distribution of relative amount of changes (number of lines added or
|
||||
removed) for each sub-directory. Directories with changes below
|
||||
a cut-off percent (3% by default) are not shown. The cut-off percent
|
||||
can be set with "--dirstat=limit". Changes in a child directory is not
|
||||
counted for the parent directory, unless "--cumulative" is used.
|
||||
|
||||
--summary::
|
||||
Output a condensed summary of extended header information
|
||||
|
||||
@@ -14,6 +14,11 @@ DESCRIPTION
|
||||
Annotates each line in the given file with information from the commit
|
||||
which introduced the line. Optionally annotate from a given revision.
|
||||
|
||||
The only difference between this command and linkgit:git-blame[1] is that
|
||||
they use slightly different output formats, and this command exists only
|
||||
for backward compatibility to support existing scripts, and provide more
|
||||
familiar command name for people coming from other SCM systems.
|
||||
|
||||
OPTIONS
|
||||
-------
|
||||
include::blame-options.txt[]
|
||||
|
||||
@@ -79,9 +79,9 @@ Diagnostics
|
||||
You don't exist. Go away!::
|
||||
The passwd(5) gecos field couldn't be read
|
||||
Your parents must have hated you!::
|
||||
The password(5) gecos field is longer than a giant static buffer.
|
||||
The passwd(5) gecos field is longer than a giant static buffer.
|
||||
Your sysadmin must hate you!::
|
||||
The password(5) name field is longer than a giant static buffer.
|
||||
The passwd(5) name field is longer than a giant static buffer.
|
||||
|
||||
Discussion
|
||||
----------
|
||||
|
||||
@@ -75,8 +75,10 @@ OPTIONS
|
||||
read the message from the standard input.
|
||||
|
||||
--author=<author>::
|
||||
Override the author name used in the commit. Use
|
||||
`A U Thor <author@example.com>` format.
|
||||
Override the author name used in the commit. You can use the
|
||||
standard `A U Thor <author@example.com>` format. Otherwise,
|
||||
an existing commit that matches the given string and its author
|
||||
name is used.
|
||||
|
||||
-m <msg>::
|
||||
--message=<msg>::
|
||||
|
||||
@@ -9,8 +9,9 @@ SYNOPSIS
|
||||
--------
|
||||
[verse]
|
||||
'git daemon' [--verbose] [--syslog] [--export-all]
|
||||
[--timeout=n] [--init-timeout=n] [--strict-paths]
|
||||
[--base-path=path] [--user-path | --user-path=path]
|
||||
[--timeout=n] [--init-timeout=n] [--max-connections=n]
|
||||
[--strict-paths] [--base-path=path] [--base-path-relaxed]
|
||||
[--user-path | --user-path=path]
|
||||
[--interpolated-path=pathtemplate]
|
||||
[--reuseaddr] [--detach] [--pid-file=file]
|
||||
[--enable=service] [--disable=service]
|
||||
@@ -99,6 +100,10 @@ OPTIONS
|
||||
it takes for the server to process the sub-request and time spent
|
||||
waiting for next client's request.
|
||||
|
||||
--max-connections::
|
||||
Maximum number of concurrent clients, defaults to 32. Set it to
|
||||
zero for no limit.
|
||||
|
||||
--syslog::
|
||||
Log to syslog instead of stderr. Note that this option does not imply
|
||||
--verbose, thus by default only error conditions will be logged.
|
||||
|
||||
@@ -16,7 +16,7 @@ DESCRIPTION
|
||||
|
||||
Iterate over all refs that match `<pattern>` and show them
|
||||
according to the given `<format>`, after sorting them according
|
||||
to the given set of `<key>`. If `<max>` is given, stop after
|
||||
to the given set of `<key>`. If `<count>` is given, stop after
|
||||
showing that many refs. The interpolated values in `<format>`
|
||||
can optionally be quoted as string literals in the specified
|
||||
host language allowing their direct evaluation in that language.
|
||||
|
||||
@@ -112,7 +112,9 @@ For example, this configuration:
|
||||
will try to use konqueror first. But this may fail (for example if
|
||||
DISPLAY is not set) and in that case emacs' woman mode will be tried.
|
||||
|
||||
If everything fails the 'man' program will be tried anyway.
|
||||
If everything fails, or if no viewer is configured, the viewer specified
|
||||
in the GIT_MAN_VIEWER environment variable will be tried. If that
|
||||
fails too, the 'man' program will be tried anyway.
|
||||
|
||||
man.<tool>.path
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -59,7 +59,7 @@ Enter 'git-name-rev':
|
||||
|
||||
------------
|
||||
% git name-rev 33db5f4d9027a10e477ccf054b2c1ab94f74c85a
|
||||
33db5f4d9027a10e477ccf054b2c1ab94f74c85a tags/v0.99^0~940
|
||||
33db5f4d9027a10e477ccf054b2c1ab94f74c85a tags/v0.99~940
|
||||
------------
|
||||
|
||||
Now you are wiser, because you know that it happened 940 revisions before v0.99.
|
||||
|
||||
@@ -179,6 +179,9 @@ user is prompted for a password while the input is masked for privacy.
|
||||
This is useful if your default address is not the address that is
|
||||
subscribed to a list. If you use the sendmail binary, you must have
|
||||
suitable privileges for the -f parameter.
|
||||
Default is the value of the 'sendemail.envelopesender' configuration
|
||||
variable; if that is unspecified, choosing the envelope sender is left
|
||||
to your MTA.
|
||||
|
||||
--to::
|
||||
Specify the primary recipient of the emails generated.
|
||||
|
||||
@@ -159,7 +159,7 @@ perform a pull, and then unstash, like this:
|
||||
+
|
||||
----------------------------------------------------------------
|
||||
$ git pull
|
||||
...
|
||||
...
|
||||
file foobar not up to date, cannot merge.
|
||||
$ git stash
|
||||
$ git pull
|
||||
@@ -174,7 +174,7 @@ make a commit to a temporary branch to store your changes away, and
|
||||
return to your original branch to make the emergency fix, like this:
|
||||
+
|
||||
----------------------------------------------------------------
|
||||
... hack hack hack ...
|
||||
# ... hack hack hack ...
|
||||
$ git checkout -b my_wip
|
||||
$ git commit -a -m "WIP"
|
||||
$ git checkout master
|
||||
@@ -182,18 +182,18 @@ $ edit emergency fix
|
||||
$ git commit -a -m "Fix in a hurry"
|
||||
$ git checkout my_wip
|
||||
$ git reset --soft HEAD^
|
||||
... continue hacking ...
|
||||
# ... continue hacking ...
|
||||
----------------------------------------------------------------
|
||||
+
|
||||
You can use 'git-stash' to simplify the above, like this:
|
||||
+
|
||||
----------------------------------------------------------------
|
||||
... hack hack hack ...
|
||||
# ... hack hack hack ...
|
||||
$ git stash
|
||||
$ edit emergency fix
|
||||
$ git commit -a -m "Fix in a hurry"
|
||||
$ git stash apply
|
||||
... continue hacking ...
|
||||
# ... continue hacking ...
|
||||
----------------------------------------------------------------
|
||||
|
||||
Testing partial commits::
|
||||
@@ -203,13 +203,13 @@ more commits out of the changes in the work tree, and you want to test
|
||||
each change before committing:
|
||||
+
|
||||
----------------------------------------------------------------
|
||||
... hack hack hack ...
|
||||
# ... hack hack hack ...
|
||||
$ git add --patch foo # add just first part to the index
|
||||
$ git stash save --keep-index # save all other changes to the stash
|
||||
$ edit/build/test first part
|
||||
$ git commit foo -m 'First part' # commit fully tested change
|
||||
$ git commit -m 'First part' # commit fully tested change
|
||||
$ git stash pop # prepare to work on all other changes
|
||||
... repeat above five steps until one commit remains ...
|
||||
# ... repeat above five steps until one commit remains ...
|
||||
$ edit/build/test remaining parts
|
||||
$ git commit foo -m 'Remaining parts'
|
||||
----------------------------------------------------------------
|
||||
|
||||
@@ -15,6 +15,7 @@ SYNOPSIS
|
||||
'git submodule' [--quiet] update [--init] [--] [<path>...]
|
||||
'git submodule' [--quiet] summary [--summary-limit <n>] [commit] [--] [<path>...]
|
||||
'git submodule' [--quiet] foreach <command>
|
||||
'git submodule' [--quiet] sync [--] [<path>...]
|
||||
|
||||
|
||||
DESCRIPTION
|
||||
@@ -139,6 +140,14 @@ foreach::
|
||||
As an example, "git submodule foreach 'echo $path `git rev-parse HEAD`' will
|
||||
show the path and currently checked out commit for each submodule.
|
||||
|
||||
sync::
|
||||
Synchronizes submodules' remote URL configuration setting
|
||||
to the value specified in .gitmodules. This is useful when
|
||||
submodule URLs change upstream and you need to update your local
|
||||
repositories accordingly.
|
||||
+
|
||||
"git submodule sync" synchronizes all submodules while
|
||||
"git submodule sync -- A" synchronizes submodule "A" only.
|
||||
|
||||
OPTIONS
|
||||
-------
|
||||
|
||||
@@ -20,7 +20,7 @@ OPTIONS
|
||||
Cause the logical variables to be listed. In addition, all the
|
||||
variables of the git configuration file .git/config are listed
|
||||
as well. (However, the configuration variables listing functionality
|
||||
is deprecated in favor of 'git-config -l'.)
|
||||
is deprecated in favor of 'git config -l'.)
|
||||
|
||||
EXAMPLE
|
||||
--------
|
||||
@@ -41,9 +41,9 @@ Diagnostics
|
||||
You don't exist. Go away!::
|
||||
The passwd(5) gecos field couldn't be read
|
||||
Your parents must have hated you!::
|
||||
The password(5) gecos field is longer than a giant static buffer.
|
||||
The passwd(5) gecos field is longer than a giant static buffer.
|
||||
Your sysadmin must hate you!::
|
||||
The password(5) name field is longer than a giant static buffer.
|
||||
The passwd(5) name field is longer than a giant static buffer.
|
||||
|
||||
SEE ALSO
|
||||
--------
|
||||
|
||||
@@ -7,7 +7,7 @@ gitattributes - defining attributes per path
|
||||
|
||||
SYNOPSIS
|
||||
--------
|
||||
$GIT_DIR/info/attributes, gitattributes
|
||||
$GIT_DIR/info/attributes, .gitattributes
|
||||
|
||||
|
||||
DESCRIPTION
|
||||
@@ -105,9 +105,8 @@ Set::
|
||||
|
||||
Unset::
|
||||
|
||||
Unsetting the `crlf` attribute on a path is meant to
|
||||
mark the path as a "binary" file. The path never goes
|
||||
through line endings conversion upon checkin/checkout.
|
||||
Unsetting the `crlf` attribute on a path tells git not to
|
||||
attempt any end-of-line conversion upon checkin or checkout.
|
||||
|
||||
Unspecified::
|
||||
|
||||
@@ -486,6 +485,41 @@ in the file. E.g. the string `$Format:%H$` will be replaced by the
|
||||
commit hash.
|
||||
|
||||
|
||||
USING ATTRIBUTE MACROS
|
||||
----------------------
|
||||
|
||||
You do not want any end-of-line conversions applied to, nor textual diffs
|
||||
produced for, any binary file you track. You would need to specify e.g.
|
||||
|
||||
------------
|
||||
*.jpg -crlf -diff
|
||||
------------
|
||||
|
||||
but that may become cumbersome, when you have many attributes. Using
|
||||
attribute macros, you can specify groups of attributes set or unset at
|
||||
the same time. The system knows a built-in attribute macro, `binary`:
|
||||
|
||||
------------
|
||||
*.jpg binary
|
||||
------------
|
||||
|
||||
which is equivalent to the above. Note that the attribute macros can only
|
||||
be "Set" (see the above example that sets "binary" macro as if it were an
|
||||
ordinary attribute --- setting it in turn unsets "crlf" and "diff").
|
||||
|
||||
|
||||
DEFINING ATTRIBUTE MACROS
|
||||
-------------------------
|
||||
|
||||
Custom attribute macros can be defined only in the `.gitattributes` file
|
||||
at the toplevel (i.e. not in any subdirectory). The built-in attribute
|
||||
macro "binary" is equivalent to:
|
||||
|
||||
------------
|
||||
[attr]binary -diff -crlf
|
||||
------------
|
||||
|
||||
|
||||
EXAMPLE
|
||||
-------
|
||||
|
||||
|
||||
@@ -49,6 +49,13 @@ frequently used options.
|
||||
the history between two branches (i.e. the HEAD and the MERGE_HEAD)
|
||||
that modify the conflicted files.
|
||||
|
||||
--argscmd=<command>::
|
||||
Command to be run each time gitk has to determine the list of
|
||||
<revs> to show. The command is expected to print on its standard
|
||||
output a list of additional revs to be shown, one per line.
|
||||
Use this instead of explicitly specifying <revs> if the set of
|
||||
commits to show may vary between refreshes.
|
||||
|
||||
<revs>::
|
||||
|
||||
Limit the revisions to show. This can be either a single revision
|
||||
|
||||
@@ -7,7 +7,7 @@ gitmodules - defining submodule properties
|
||||
|
||||
SYNOPSIS
|
||||
--------
|
||||
gitmodules
|
||||
$GIT_WORK_DIR/.gitmodules
|
||||
|
||||
|
||||
DESCRIPTION
|
||||
|
||||
@@ -321,10 +321,37 @@ pulling, like this:
|
||||
|
||||
------------------------------------------------
|
||||
alice$ git fetch /home/bob/myrepo master
|
||||
alice$ git log -p ..FETCH_HEAD
|
||||
alice$ git log -p HEAD..FETCH_HEAD
|
||||
------------------------------------------------
|
||||
|
||||
This operation is safe even if Alice has uncommitted local changes.
|
||||
The range notation HEAD..FETCH_HEAD" means "show everything that is reachable
|
||||
from the FETCH_HEAD but exclude anything that is reachable from HEAD.
|
||||
Alice already knows everything that leads to her current state (HEAD),
|
||||
and reviewing what Bob has in his state (FETCH_HEAD) that she has not
|
||||
seen with this command
|
||||
|
||||
If Alice wants to visualize what Bob did since their histories forked
|
||||
she can issue the following command:
|
||||
|
||||
------------------------------------------------
|
||||
$ gitk HEAD..FETCH_HEAD
|
||||
------------------------------------------------
|
||||
|
||||
This uses the same two-dot range notation we saw earlier with 'git log'.
|
||||
|
||||
Alice may want to view what both of them did since they forked.
|
||||
She can use three-dot form instead of the two-dot form:
|
||||
|
||||
------------------------------------------------
|
||||
$ gitk HEAD...FETCH_HEAD
|
||||
------------------------------------------------
|
||||
|
||||
This means "show everything that is reachable from either one, but
|
||||
exclude anything that is reachable from both of them".
|
||||
|
||||
Please note that these range notation can be used with both gitk
|
||||
and "git log".
|
||||
|
||||
After inspecting what Bob did, if there is nothing urgent, Alice may
|
||||
decide to continue working without pulling from Bob. If Bob's history
|
||||
|
||||
@@ -21,7 +21,7 @@ project find it more convenient to use legacy encodings, git
|
||||
does not forbid it. However, there are a few things to keep in
|
||||
mind.
|
||||
|
||||
. 'git-commit-tree' (hence, 'git-commit' which uses it) issues
|
||||
. 'git-commit' and 'git-commit-tree' issues
|
||||
a warning if the commit log message given to it does not look
|
||||
like a valid UTF-8 string, unless you explicitly say your
|
||||
project uses a legacy encoding. The way to say this is to
|
||||
|
||||
@@ -103,7 +103,7 @@ The placeholders are:
|
||||
- '%an': author name
|
||||
- '%aN': author name (respecting .mailmap)
|
||||
- '%ae': author email
|
||||
- '%ad': author date
|
||||
- '%ad': author date (format respects --date= option)
|
||||
- '%aD': author date, RFC2822 style
|
||||
- '%ar': author date, relative
|
||||
- '%at': author date, UNIX timestamp
|
||||
|
||||
@@ -409,6 +409,48 @@ Note that without '\--full-history', this still simplifies merges: if
|
||||
one of the parents is TREESAME, we follow only that one, so the other
|
||||
sides of the merge are never walked.
|
||||
|
||||
Finally, there is a fourth simplification mode available:
|
||||
|
||||
--simplify-merges::
|
||||
|
||||
First, build a history graph in the same way that
|
||||
'\--full-history' with parent rewriting does (see above).
|
||||
+
|
||||
Then simplify each commit `C` to its replacement `C'` in the final
|
||||
history according to the following rules:
|
||||
+
|
||||
--
|
||||
* Set `C'` to `C`.
|
||||
+
|
||||
* Replace each parent `P` of `C'` with its simplification `P'`. In
|
||||
the process, drop parents that are ancestors of other parents, and
|
||||
remove duplicates.
|
||||
+
|
||||
* If after this parent rewriting, `C'` is a root or merge commit (has
|
||||
zero or >1 parents), a boundary commit, or !TREESAME, it remains.
|
||||
Otherwise, it is replaced with its only parent.
|
||||
--
|
||||
+
|
||||
The effect of this is best shown by way of comparing to
|
||||
'\--full-history' with parent rewriting. The example turns into:
|
||||
+
|
||||
-----------------------------------------------------------------------
|
||||
.-A---M---N---O
|
||||
/ / /
|
||||
I B D
|
||||
\ / /
|
||||
`---------'
|
||||
-----------------------------------------------------------------------
|
||||
+
|
||||
Note the major differences in `N` and `P` over '\--full-history':
|
||||
+
|
||||
--
|
||||
* `N`'s parent list had `I` removed, because it is an ancestor of the
|
||||
other parent `M`. Still, `N` remained because it is !TREESAME.
|
||||
+
|
||||
* `P`'s parent list similarly had `I` removed. `P` was then
|
||||
removed completely, because it had one parent and is TREESAME.
|
||||
--
|
||||
|
||||
ifdef::git-rev-list[]
|
||||
Bisection Helpers
|
||||
|
||||
7
Makefile
7
Makefile
@@ -357,10 +357,12 @@ LIB_H += git-compat-util.h
|
||||
LIB_H += graph.h
|
||||
LIB_H += grep.h
|
||||
LIB_H += hash.h
|
||||
LIB_H += help.h
|
||||
LIB_H += list-objects.h
|
||||
LIB_H += ll-merge.h
|
||||
LIB_H += log-tree.h
|
||||
LIB_H += mailmap.h
|
||||
LIB_H += merge-recursive.h
|
||||
LIB_H += object.h
|
||||
LIB_H += pack.h
|
||||
LIB_H += pack-refs.h
|
||||
@@ -520,6 +522,7 @@ BUILTIN_OBJS += builtin-for-each-ref.o
|
||||
BUILTIN_OBJS += builtin-fsck.o
|
||||
BUILTIN_OBJS += builtin-gc.o
|
||||
BUILTIN_OBJS += builtin-grep.o
|
||||
BUILTIN_OBJS += builtin-help.o
|
||||
BUILTIN_OBJS += builtin-init-db.o
|
||||
BUILTIN_OBJS += builtin-log.o
|
||||
BUILTIN_OBJS += builtin-ls-files.o
|
||||
@@ -1103,7 +1106,7 @@ git$X: git.o $(BUILTIN_OBJS) $(GITLIBS)
|
||||
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ git.o \
|
||||
$(BUILTIN_OBJS) $(ALL_LDFLAGS) $(LIBS)
|
||||
|
||||
help.o: help.c common-cmds.h GIT-CFLAGS
|
||||
builtin-help.o: builtin-help.c common-cmds.h GIT-CFLAGS
|
||||
$(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) \
|
||||
'-DGIT_HTML_PATH="$(htmldir_SQ)"' \
|
||||
'-DGIT_MAN_PATH="$(mandir_SQ)"' \
|
||||
@@ -1377,7 +1380,7 @@ endif
|
||||
{ $(RM) "$$execdir/git-add$X" && \
|
||||
ln git-add$X "$$execdir/git-add$X" 2>/dev/null || \
|
||||
cp git-add$X "$$execdir/git-add$X"; } && \
|
||||
{ $(foreach p,$(filter-out git-add,$(BUILT_INS)), $(RM) "$$execdir/$p" && \
|
||||
{ $(foreach p,$(filter-out git-add$X,$(BUILT_INS)), $(RM) "$$execdir/$p" && \
|
||||
ln "$$execdir/git-add$X" "$$execdir/$p" 2>/dev/null || \
|
||||
ln -s "git-add$X" "$$execdir/$p" 2>/dev/null || \
|
||||
cp "$$execdir/git-add$X" "$$execdir/$p" || exit;) } && \
|
||||
|
||||
@@ -48,7 +48,7 @@ static void format_subst(const struct commit *commit,
|
||||
strbuf_add(&fmt, b + 8, c - b - 8);
|
||||
|
||||
strbuf_add(buf, src, b - src);
|
||||
format_commit_message(commit, fmt.buf, buf);
|
||||
format_commit_message(commit, fmt.buf, buf, DATE_NORMAL);
|
||||
len -= c + 1 - src;
|
||||
src = c + 1;
|
||||
}
|
||||
|
||||
100
builtin-add.c
100
builtin-add.c
@@ -8,10 +8,6 @@
|
||||
#include "dir.h"
|
||||
#include "exec_cmd.h"
|
||||
#include "cache-tree.h"
|
||||
#include "diff.h"
|
||||
#include "diffcore.h"
|
||||
#include "commit.h"
|
||||
#include "revision.h"
|
||||
#include "run-command.h"
|
||||
#include "parse-options.h"
|
||||
|
||||
@@ -22,6 +18,27 @@ static const char * const builtin_add_usage[] = {
|
||||
static int patch_interactive = 0, add_interactive = 0;
|
||||
static int take_worktree_changes;
|
||||
|
||||
static void fill_pathspec_matches(const char **pathspec, char *seen, int specs)
|
||||
{
|
||||
int num_unmatched = 0, i;
|
||||
|
||||
/*
|
||||
* Since we are walking the index as if we are warlking the directory,
|
||||
* we have to mark the matched pathspec as seen; otherwise we will
|
||||
* mistakenly think that the user gave a pathspec that did not match
|
||||
* anything.
|
||||
*/
|
||||
for (i = 0; i < specs; i++)
|
||||
if (!seen[i])
|
||||
num_unmatched++;
|
||||
if (!num_unmatched)
|
||||
return;
|
||||
for (i = 0; i < active_nr; i++) {
|
||||
struct cache_entry *ce = active_cache[i];
|
||||
match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen);
|
||||
}
|
||||
}
|
||||
|
||||
static void prune_directory(struct dir_struct *dir, const char **pathspec, int prefix)
|
||||
{
|
||||
char *seen;
|
||||
@@ -41,6 +58,7 @@ static void prune_directory(struct dir_struct *dir, const char **pathspec, int p
|
||||
*dst++ = entry;
|
||||
}
|
||||
dir->nr = dst - dir->entries;
|
||||
fill_pathspec_matches(pathspec, seen, specs);
|
||||
|
||||
for (i = 0; i < specs; i++) {
|
||||
if (!seen[i] && !file_exists(pathspec[i]))
|
||||
@@ -79,59 +97,6 @@ static void fill_directory(struct dir_struct *dir, const char **pathspec,
|
||||
prune_directory(dir, pathspec, baselen);
|
||||
}
|
||||
|
||||
struct update_callback_data
|
||||
{
|
||||
int flags;
|
||||
int add_errors;
|
||||
};
|
||||
|
||||
static void update_callback(struct diff_queue_struct *q,
|
||||
struct diff_options *opt, void *cbdata)
|
||||
{
|
||||
int i;
|
||||
struct update_callback_data *data = cbdata;
|
||||
|
||||
for (i = 0; i < q->nr; i++) {
|
||||
struct diff_filepair *p = q->queue[i];
|
||||
const char *path = p->one->path;
|
||||
switch (p->status) {
|
||||
default:
|
||||
die("unexpected diff status %c", p->status);
|
||||
case DIFF_STATUS_UNMERGED:
|
||||
case DIFF_STATUS_MODIFIED:
|
||||
case DIFF_STATUS_TYPE_CHANGED:
|
||||
if (add_file_to_cache(path, data->flags)) {
|
||||
if (!(data->flags & ADD_CACHE_IGNORE_ERRORS))
|
||||
die("updating files failed");
|
||||
data->add_errors++;
|
||||
}
|
||||
break;
|
||||
case DIFF_STATUS_DELETED:
|
||||
if (!(data->flags & ADD_CACHE_PRETEND))
|
||||
remove_file_from_cache(path);
|
||||
if (data->flags & (ADD_CACHE_PRETEND|ADD_CACHE_VERBOSE))
|
||||
printf("remove '%s'\n", path);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int add_files_to_cache(const char *prefix, const char **pathspec, int flags)
|
||||
{
|
||||
struct update_callback_data data;
|
||||
struct rev_info rev;
|
||||
init_revisions(&rev, prefix);
|
||||
setup_revisions(0, NULL, &rev, NULL);
|
||||
rev.prune_data = pathspec;
|
||||
rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
|
||||
rev.diffopt.format_callback = update_callback;
|
||||
data.flags = flags;
|
||||
data.add_errors = 0;
|
||||
rev.diffopt.format_callback_data = &data;
|
||||
run_diff_files(&rev, DIFF_RACY_IS_MODIFIED);
|
||||
return !!data.add_errors;
|
||||
}
|
||||
|
||||
static void refresh(int verbose, const char **pathspec)
|
||||
{
|
||||
char *seen;
|
||||
@@ -268,7 +233,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
|
||||
|
||||
if (addremove && take_worktree_changes)
|
||||
die("-A and -u are mutually incompatible");
|
||||
if (addremove && !argc) {
|
||||
if ((addremove || take_worktree_changes) && !argc) {
|
||||
static const char *here[2] = { ".", NULL };
|
||||
argc = 1;
|
||||
argv = here;
|
||||
@@ -281,7 +246,9 @@ int cmd_add(int argc, const char **argv, const char *prefix)
|
||||
|
||||
flags = ((verbose ? ADD_CACHE_VERBOSE : 0) |
|
||||
(show_only ? ADD_CACHE_PRETEND : 0) |
|
||||
(ignore_add_errors ? ADD_CACHE_IGNORE_ERRORS : 0));
|
||||
(ignore_add_errors ? ADD_CACHE_IGNORE_ERRORS : 0) |
|
||||
(!(addremove || take_worktree_changes)
|
||||
? ADD_CACHE_IGNORE_REMOVAL : 0));
|
||||
|
||||
if (require_pathspec && argc == 0) {
|
||||
fprintf(stderr, "Nothing specified, nothing added.\n");
|
||||
@@ -290,24 +257,19 @@ int cmd_add(int argc, const char **argv, const char *prefix)
|
||||
}
|
||||
pathspec = validate_pathspec(argc, argv, prefix);
|
||||
|
||||
/*
|
||||
* If we are adding new files, we need to scan the working
|
||||
* tree to find the ones that match pathspecs; this needs
|
||||
* to be done before we read the index.
|
||||
*/
|
||||
if (add_new_files)
|
||||
fill_directory(&dir, pathspec, ignored_too);
|
||||
|
||||
if (read_cache() < 0)
|
||||
die("index file corrupt");
|
||||
|
||||
if (add_new_files)
|
||||
/* This picks up the paths that are not tracked */
|
||||
fill_directory(&dir, pathspec, ignored_too);
|
||||
|
||||
if (refresh_only) {
|
||||
refresh(verbose, pathspec);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (take_worktree_changes || addremove)
|
||||
exit_status |= add_files_to_cache(prefix, pathspec, flags);
|
||||
exit_status |= add_files_to_cache(prefix, pathspec, flags);
|
||||
|
||||
if (add_new_files)
|
||||
exit_status |= add_files(&dir, flags);
|
||||
|
||||
@@ -274,7 +274,7 @@ static void say_patch_name(FILE *output, const char *pre,
|
||||
static void read_patch_file(struct strbuf *sb, int fd)
|
||||
{
|
||||
if (strbuf_read(sb, fd, 0) < 0)
|
||||
die("git-apply: read returned %s", strerror(errno));
|
||||
die("git apply: read returned %s", strerror(errno));
|
||||
|
||||
/*
|
||||
* Make sure that we have some slop in the buffer
|
||||
@@ -506,17 +506,17 @@ static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name,
|
||||
name = orig_name;
|
||||
len = strlen(name);
|
||||
if (isnull)
|
||||
die("git-apply: bad git-diff - expected /dev/null, got %s on line %d", name, linenr);
|
||||
die("git apply: bad git-diff - expected /dev/null, got %s on line %d", name, linenr);
|
||||
another = find_name(line, NULL, p_value, TERM_TAB);
|
||||
if (!another || memcmp(another, name, len))
|
||||
die("git-apply: bad git-diff - inconsistent %s filename on line %d", oldnew, linenr);
|
||||
die("git apply: bad git-diff - inconsistent %s filename on line %d", oldnew, linenr);
|
||||
free(another);
|
||||
return orig_name;
|
||||
}
|
||||
else {
|
||||
/* expect "/dev/null" */
|
||||
if (memcmp("/dev/null", line, 9) || line[9] != '\n')
|
||||
die("git-apply: bad git-diff - expected /dev/null on line %d", linenr);
|
||||
die("git apply: bad git-diff - expected /dev/null on line %d", linenr);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
@@ -1996,6 +1996,8 @@ static int apply_one_fragment(struct image *img, struct fragment *frag,
|
||||
/*
|
||||
* A hunk to change lines at the beginning would begin with
|
||||
* @@ -1,L +N,M @@
|
||||
* but we need to be careful. -U0 that inserts before the second
|
||||
* line also has this pattern.
|
||||
*
|
||||
* And a hunk to add to an empty file would begin with
|
||||
* @@ -0,0 +N,M @@
|
||||
@@ -2003,7 +2005,8 @@ static int apply_one_fragment(struct image *img, struct fragment *frag,
|
||||
* In other words, a hunk that is (frag->oldpos <= 1) with or
|
||||
* without leading context must match at the beginning.
|
||||
*/
|
||||
match_beginning = frag->oldpos <= 1;
|
||||
match_beginning = (!frag->oldpos ||
|
||||
(frag->oldpos == 1 && !unidiff_zero));
|
||||
|
||||
/*
|
||||
* A hunk without trailing lines must match at the end.
|
||||
|
||||
@@ -47,18 +47,18 @@ static int run_remote_archiver(const char *remote, int argc,
|
||||
|
||||
len = packet_read_line(fd[0], buf, sizeof(buf));
|
||||
if (!len)
|
||||
die("git-archive: expected ACK/NAK, got EOF");
|
||||
die("git archive: expected ACK/NAK, got EOF");
|
||||
if (buf[len-1] == '\n')
|
||||
buf[--len] = 0;
|
||||
if (strcmp(buf, "ACK")) {
|
||||
if (len > 5 && !prefixcmp(buf, "NACK "))
|
||||
die("git-archive: NACK %s", buf + 5);
|
||||
die("git-archive: protocol error");
|
||||
die("git archive: NACK %s", buf + 5);
|
||||
die("git archive: protocol error");
|
||||
}
|
||||
|
||||
len = packet_read_line(fd[0], buf, sizeof(buf));
|
||||
if (len)
|
||||
die("git-archive: expected a flush");
|
||||
die("git archive: expected a flush");
|
||||
|
||||
/* Now, start reading from fd[0] and spit it out to stdout */
|
||||
rv = recv_sideband("archive", fd[0], 1, 2);
|
||||
|
||||
@@ -38,7 +38,6 @@ static int show_root;
|
||||
static int reverse;
|
||||
static int blank_boundary;
|
||||
static int incremental;
|
||||
static int cmd_is_annotate;
|
||||
static int xdl_opts = XDF_NEED_MINIMAL;
|
||||
static struct string_list mailmap;
|
||||
|
||||
@@ -1682,7 +1681,7 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt)
|
||||
if (suspect->commit->object.flags & UNINTERESTING) {
|
||||
if (blank_boundary)
|
||||
memset(hex, ' ', length);
|
||||
else if (!cmd_is_annotate) {
|
||||
else if (!(opt & OUTPUT_ANNOTATE_COMPAT)) {
|
||||
length--;
|
||||
putchar('^');
|
||||
}
|
||||
@@ -1787,7 +1786,7 @@ static int prepare_lines(struct scoreboard *sb)
|
||||
|
||||
/*
|
||||
* Add phony grafts for use with -S; this is primarily to
|
||||
* support git-cvsserver that wants to give a linear history
|
||||
* support git's cvsserver that wants to give a linear history
|
||||
* to its clients.
|
||||
*/
|
||||
static int read_ancestry(const char *graft_file)
|
||||
@@ -2313,8 +2312,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
|
||||
};
|
||||
|
||||
struct parse_opt_ctx_t ctx;
|
||||
|
||||
cmd_is_annotate = !strcmp(argv[0], "annotate");
|
||||
int cmd_is_annotate = !strcmp(argv[0], "annotate");
|
||||
|
||||
git_config(git_blame_config, NULL);
|
||||
init_revisions(&revs, NULL);
|
||||
@@ -2342,6 +2340,9 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
|
||||
parse_done:
|
||||
argc = parse_options_end(&ctx);
|
||||
|
||||
if (cmd_is_annotate)
|
||||
output_option |= OUTPUT_ANNOTATE_COMPAT;
|
||||
|
||||
if (DIFF_OPT_TST(&revs.diffopt, FIND_COPIES_HARDER))
|
||||
opt |= (PICKAXE_BLAME_COPY | PICKAXE_BLAME_MOVE |
|
||||
PICKAXE_BLAME_COPY_HARDER);
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
* Basic handler for bundle files to connect repositories via sneakernet.
|
||||
* Invocation must include action.
|
||||
* This function can create a bundle or provide information on an existing
|
||||
* bundle supporting git-fetch, git-pull, and git-ls-remote
|
||||
* bundle supporting "fetch", "pull", and "ls-remote".
|
||||
*/
|
||||
|
||||
static const char *bundle_usage="git-bundle (create <bundle> <git-rev-list args> | verify <bundle> | list-heads <bundle> [refname]... | unbundle <bundle> [refname]... )";
|
||||
static const char *bundle_usage="git bundle (create <bundle> <git rev-list args> | verify <bundle> | list-heads <bundle> [refname]... | unbundle <bundle> [refname]... )";
|
||||
|
||||
int cmd_bundle(int argc, const char **argv, const char *prefix)
|
||||
{
|
||||
|
||||
@@ -137,11 +137,11 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name)
|
||||
break;
|
||||
|
||||
default:
|
||||
die("git-cat-file: unknown option: %s\n", exp_type);
|
||||
die("git cat-file: unknown option: %s\n", exp_type);
|
||||
}
|
||||
|
||||
if (!buf)
|
||||
die("git-cat-file %s: bad file", obj_name);
|
||||
die("git cat-file %s: bad file", obj_name);
|
||||
|
||||
write_or_die(1, buf, size);
|
||||
return 0;
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
int cmd_check_ref_format(int argc, const char **argv, const char *prefix)
|
||||
{
|
||||
if (argc != 2)
|
||||
usage("git-check-ref-format refname");
|
||||
usage("git check-ref-format refname");
|
||||
return !!check_ref_format(argv[1]);
|
||||
}
|
||||
|
||||
@@ -258,9 +258,9 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
|
||||
const char *p;
|
||||
|
||||
if (all)
|
||||
die("git-checkout-index: don't mix '--all' and explicit filenames");
|
||||
die("git checkout-index: don't mix '--all' and explicit filenames");
|
||||
if (read_from_stdin)
|
||||
die("git-checkout-index: don't mix '--stdin' and explicit filenames");
|
||||
die("git checkout-index: don't mix '--stdin' and explicit filenames");
|
||||
p = prefix_path(prefix, prefix_length, arg);
|
||||
checkout_file(p, prefix_length);
|
||||
if (p < arg || p > arg + strlen(arg))
|
||||
@@ -271,7 +271,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
|
||||
struct strbuf buf, nbuf;
|
||||
|
||||
if (all)
|
||||
die("git-checkout-index: don't mix '--all' and '--stdin'");
|
||||
die("git checkout-index: don't mix '--all' and '--stdin'");
|
||||
|
||||
strbuf_init(&buf, 0);
|
||||
strbuf_init(&nbuf, 0);
|
||||
|
||||
@@ -386,13 +386,11 @@ static int switch_branches(struct checkout_opts *opts, struct branch_info *new)
|
||||
}
|
||||
|
||||
/*
|
||||
* If the new thing isn't a branch and isn't HEAD and we're
|
||||
* not starting a new branch, and we want messages, and we
|
||||
* weren't on a branch, and we're moving to a new commit,
|
||||
* describe the old commit.
|
||||
* If we were on a detached HEAD, but we are now moving to
|
||||
* a new commit, we want to mention the old commit once more
|
||||
* to remind the user that it might be lost.
|
||||
*/
|
||||
if (!new->path && strcmp(new->name, "HEAD") && !opts->new_branch &&
|
||||
!opts->quiet && !old.path && new->commit != old.commit)
|
||||
if (!opts->quiet && !old.path && new->commit != old.commit)
|
||||
describe_detached_head("Previous HEAD position was", old.commit);
|
||||
|
||||
if (!old.commit) {
|
||||
|
||||
@@ -147,6 +147,15 @@ static int is_directory(const char *path)
|
||||
return !stat(path, &buf) && S_ISDIR(buf.st_mode);
|
||||
}
|
||||
|
||||
static void strip_trailing_slashes(char *dir)
|
||||
{
|
||||
char *end = dir + strlen(dir);
|
||||
|
||||
while (dir < end - 1 && is_dir_sep(end[-1]))
|
||||
end--;
|
||||
*end = '\0';
|
||||
}
|
||||
|
||||
static void setup_reference(const char *repo)
|
||||
{
|
||||
const char *ref_git;
|
||||
@@ -387,7 +396,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
|
||||
|
||||
path = get_repo_path(repo_name, &is_bundle);
|
||||
if (path)
|
||||
repo = path;
|
||||
repo = xstrdup(make_nonrelative_path(repo_name));
|
||||
else if (!strchr(repo_name, ':'))
|
||||
repo = xstrdup(make_absolute_path(repo_name));
|
||||
else
|
||||
@@ -397,6 +406,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
|
||||
dir = xstrdup(argv[1]);
|
||||
else
|
||||
dir = guess_dir_name(repo_name, is_bundle, option_bare);
|
||||
strip_trailing_slashes(dir);
|
||||
|
||||
if (!stat(dir, &buf))
|
||||
die("destination directory '%s' already exists.", dir);
|
||||
@@ -422,10 +432,11 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
|
||||
if (!option_bare) {
|
||||
junk_work_tree = work_tree;
|
||||
if (safe_create_leading_directories_const(work_tree) < 0)
|
||||
die("could not create leading directories of '%s'",
|
||||
work_tree);
|
||||
die("could not create leading directories of '%s': %s",
|
||||
work_tree, strerror(errno));
|
||||
if (mkdir(work_tree, 0755))
|
||||
die("could not create work tree dir '%s'.", work_tree);
|
||||
die("could not create work tree dir '%s': %s.",
|
||||
work_tree, strerror(errno));
|
||||
set_git_work_tree(work_tree);
|
||||
}
|
||||
junk_git_dir = git_dir;
|
||||
|
||||
@@ -121,7 +121,7 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
|
||||
}
|
||||
|
||||
if (strbuf_read(&buffer, 0, 0) < 0)
|
||||
die("git-commit-tree: read returned %s", strerror(errno));
|
||||
die("git commit-tree: read returned %s", strerror(errno));
|
||||
|
||||
if (!commit_tree(buffer.buf, tree_sha1, parents, commit_sha1)) {
|
||||
printf("%s\n", sha1_to_hex(commit_sha1));
|
||||
|
||||
@@ -320,7 +320,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix)
|
||||
die("unable to write new_index file");
|
||||
|
||||
fd = hold_lock_file_for_update(&false_lock,
|
||||
git_path("next-index-%d", getpid()), 1);
|
||||
git_path("next-index-%"PRIuMAX, (uintmax_t) getpid()), 1);
|
||||
|
||||
create_base_index();
|
||||
add_remove_files(&partial);
|
||||
@@ -710,6 +710,31 @@ static int message_is_empty(struct strbuf *sb, int start)
|
||||
return 1;
|
||||
}
|
||||
|
||||
static const char *find_author_by_nickname(const char *name)
|
||||
{
|
||||
struct rev_info revs;
|
||||
struct commit *commit;
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
const char *av[20];
|
||||
int ac = 0;
|
||||
|
||||
init_revisions(&revs, NULL);
|
||||
strbuf_addf(&buf, "--author=%s", name);
|
||||
av[++ac] = "--all";
|
||||
av[++ac] = "-i";
|
||||
av[++ac] = buf.buf;
|
||||
av[++ac] = NULL;
|
||||
setup_revisions(ac, av, &revs, NULL);
|
||||
prepare_revision_walk(&revs);
|
||||
commit = get_revision(&revs);
|
||||
if (commit) {
|
||||
strbuf_release(&buf);
|
||||
format_commit_message(commit, "%an <%ae>", &buf, DATE_NORMAL);
|
||||
return strbuf_detach(&buf, NULL);
|
||||
}
|
||||
die("No existing author found with '%s'", name);
|
||||
}
|
||||
|
||||
static int parse_and_validate_options(int argc, const char *argv[],
|
||||
const char * const usage[],
|
||||
const char *prefix)
|
||||
@@ -720,6 +745,9 @@ static int parse_and_validate_options(int argc, const char *argv[],
|
||||
logfile = parse_options_fix_filename(prefix, logfile);
|
||||
template_file = parse_options_fix_filename(prefix, template_file);
|
||||
|
||||
if (force_author && !strchr(force_author, '>'))
|
||||
force_author = find_author_by_nickname(force_author);
|
||||
|
||||
if (logfile || message.len || use_message)
|
||||
use_editor = 0;
|
||||
if (edit_flag)
|
||||
@@ -882,7 +910,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1)
|
||||
|
||||
if (!log_tree_commit(&rev, commit)) {
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
format_commit_message(commit, "%h: %s", &buf);
|
||||
format_commit_message(commit, "%h: %s", &buf, DATE_NORMAL);
|
||||
printf("%s\n", buf.buf);
|
||||
strbuf_release(&buf);
|
||||
}
|
||||
|
||||
@@ -39,6 +39,8 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix)
|
||||
if (rev.pending.nr != 1 ||
|
||||
rev.max_count != -1 || rev.min_age != -1 || rev.max_age != -1)
|
||||
usage(diff_cache_usage);
|
||||
if (!cached)
|
||||
setup_work_tree();
|
||||
if (read_cache() < 0) {
|
||||
perror("read_cache");
|
||||
return -1;
|
||||
|
||||
@@ -122,6 +122,8 @@ static int builtin_diff_index(struct rev_info *revs,
|
||||
usage(builtin_diff_usage);
|
||||
argv++; argc--;
|
||||
}
|
||||
if (!cached)
|
||||
setup_work_tree();
|
||||
/*
|
||||
* Make sure there is one revision (i.e. pending object),
|
||||
* and there is no revision filtering parameters.
|
||||
@@ -225,6 +227,7 @@ static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv
|
||||
(revs->diffopt.output_format & DIFF_FORMAT_PATCH))
|
||||
revs->combine_merges = revs->dense_combined_merges = 1;
|
||||
|
||||
setup_work_tree();
|
||||
if (read_cache() < 0) {
|
||||
perror("read_cache");
|
||||
return -1;
|
||||
|
||||
@@ -540,7 +540,7 @@ static int get_pack(int xd[2], char **pack_lockfile)
|
||||
*av++ = "--fix-thin";
|
||||
if (args.lock_pack || unpack_limit) {
|
||||
int s = sprintf(keep_arg,
|
||||
"--keep=fetch-pack %d on ", getpid());
|
||||
"--keep=fetch-pack %"PRIuMAX " on ", (uintmax_t) getpid());
|
||||
if (gethostname(keep_arg + s, sizeof(keep_arg) - s))
|
||||
strcpy(keep_arg + s, "localhost");
|
||||
*av++ = keep_arg;
|
||||
@@ -609,7 +609,7 @@ static struct ref *do_fetch_pack(int fd[2],
|
||||
fprintf(stderr, "warning: no common commits\n");
|
||||
|
||||
if (get_pack(fd, pack_lockfile))
|
||||
die("git-fetch-pack: fetch failed.");
|
||||
die("git fetch-pack: fetch failed.");
|
||||
|
||||
all_done:
|
||||
return ref;
|
||||
|
||||
@@ -652,7 +652,8 @@ static int grab_single_ref(const char *refname, const unsigned char *sha1, int f
|
||||
if ((plen <= namelen) &&
|
||||
!strncmp(refname, p, plen) &&
|
||||
(refname[plen] == '\0' ||
|
||||
refname[plen] == '/'))
|
||||
refname[plen] == '/' ||
|
||||
p[plen-1] == '/'))
|
||||
break;
|
||||
if (!fnmatch(p, refname, FNM_PATHNAME))
|
||||
break;
|
||||
|
||||
@@ -774,7 +774,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
|
||||
/* Make sure we do not get outside of paths */
|
||||
for (i = 0; paths[i]; i++)
|
||||
if (strncmp(prefix, paths[i], opt.prefix_length))
|
||||
die("git-grep: cannot generate relative filenames containing '..'");
|
||||
die("git grep: cannot generate relative filenames containing '..'");
|
||||
}
|
||||
}
|
||||
else if (prefix) {
|
||||
@@ -783,8 +783,11 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
|
||||
paths[1] = NULL;
|
||||
}
|
||||
|
||||
if (!list.nr)
|
||||
if (!list.nr) {
|
||||
if (!cached)
|
||||
setup_work_tree();
|
||||
return !grep_cache(&opt, paths, cached);
|
||||
}
|
||||
|
||||
if (cached)
|
||||
die("both --cached and trees are given.");
|
||||
|
||||
465
builtin-help.c
Normal file
465
builtin-help.c
Normal file
@@ -0,0 +1,465 @@
|
||||
/*
|
||||
* builtin-help.c
|
||||
*
|
||||
* Builtin help command
|
||||
*/
|
||||
#include "cache.h"
|
||||
#include "builtin.h"
|
||||
#include "exec_cmd.h"
|
||||
#include "common-cmds.h"
|
||||
#include "parse-options.h"
|
||||
#include "run-command.h"
|
||||
#include "help.h"
|
||||
|
||||
static struct man_viewer_list {
|
||||
struct man_viewer_list *next;
|
||||
char name[FLEX_ARRAY];
|
||||
} *man_viewer_list;
|
||||
|
||||
static struct man_viewer_info_list {
|
||||
struct man_viewer_info_list *next;
|
||||
const char *info;
|
||||
char name[FLEX_ARRAY];
|
||||
} *man_viewer_info_list;
|
||||
|
||||
enum help_format {
|
||||
HELP_FORMAT_MAN,
|
||||
HELP_FORMAT_INFO,
|
||||
HELP_FORMAT_WEB,
|
||||
};
|
||||
|
||||
static int show_all = 0;
|
||||
static enum help_format help_format = HELP_FORMAT_MAN;
|
||||
static struct option builtin_help_options[] = {
|
||||
OPT_BOOLEAN('a', "all", &show_all, "print all available commands"),
|
||||
OPT_SET_INT('m', "man", &help_format, "show man page", HELP_FORMAT_MAN),
|
||||
OPT_SET_INT('w', "web", &help_format, "show manual in web browser",
|
||||
HELP_FORMAT_WEB),
|
||||
OPT_SET_INT('i', "info", &help_format, "show info page",
|
||||
HELP_FORMAT_INFO),
|
||||
OPT_END(),
|
||||
};
|
||||
|
||||
static const char * const builtin_help_usage[] = {
|
||||
"git help [--all] [--man|--web|--info] [command]",
|
||||
NULL
|
||||
};
|
||||
|
||||
static enum help_format parse_help_format(const char *format)
|
||||
{
|
||||
if (!strcmp(format, "man"))
|
||||
return HELP_FORMAT_MAN;
|
||||
if (!strcmp(format, "info"))
|
||||
return HELP_FORMAT_INFO;
|
||||
if (!strcmp(format, "web") || !strcmp(format, "html"))
|
||||
return HELP_FORMAT_WEB;
|
||||
die("unrecognized help format '%s'", format);
|
||||
}
|
||||
|
||||
static const char *get_man_viewer_info(const char *name)
|
||||
{
|
||||
struct man_viewer_info_list *viewer;
|
||||
|
||||
for (viewer = man_viewer_info_list; viewer; viewer = viewer->next)
|
||||
{
|
||||
if (!strcasecmp(name, viewer->name))
|
||||
return viewer->info;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int check_emacsclient_version(void)
|
||||
{
|
||||
struct strbuf buffer = STRBUF_INIT;
|
||||
struct child_process ec_process;
|
||||
const char *argv_ec[] = { "emacsclient", "--version", NULL };
|
||||
int version;
|
||||
|
||||
/* emacsclient prints its version number on stderr */
|
||||
memset(&ec_process, 0, sizeof(ec_process));
|
||||
ec_process.argv = argv_ec;
|
||||
ec_process.err = -1;
|
||||
ec_process.stdout_to_stderr = 1;
|
||||
if (start_command(&ec_process)) {
|
||||
fprintf(stderr, "Failed to start emacsclient.\n");
|
||||
return -1;
|
||||
}
|
||||
strbuf_read(&buffer, ec_process.err, 20);
|
||||
close(ec_process.err);
|
||||
|
||||
/*
|
||||
* Don't bother checking return value, because "emacsclient --version"
|
||||
* seems to always exits with code 1.
|
||||
*/
|
||||
finish_command(&ec_process);
|
||||
|
||||
if (prefixcmp(buffer.buf, "emacsclient")) {
|
||||
fprintf(stderr, "Failed to parse emacsclient version.\n");
|
||||
strbuf_release(&buffer);
|
||||
return -1;
|
||||
}
|
||||
|
||||
strbuf_remove(&buffer, 0, strlen("emacsclient"));
|
||||
version = atoi(buffer.buf);
|
||||
|
||||
if (version < 22) {
|
||||
fprintf(stderr,
|
||||
"emacsclient version '%d' too old (< 22).\n",
|
||||
version);
|
||||
strbuf_release(&buffer);
|
||||
return -1;
|
||||
}
|
||||
|
||||
strbuf_release(&buffer);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void exec_woman_emacs(const char* path, const char *page)
|
||||
{
|
||||
if (!check_emacsclient_version()) {
|
||||
/* This works only with emacsclient version >= 22. */
|
||||
struct strbuf man_page = STRBUF_INIT;
|
||||
|
||||
if (!path)
|
||||
path = "emacsclient";
|
||||
strbuf_addf(&man_page, "(woman \"%s\")", page);
|
||||
execlp(path, "emacsclient", "-e", man_page.buf, NULL);
|
||||
warning("failed to exec '%s': %s", path, strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
static void exec_man_konqueror(const char* path, const char *page)
|
||||
{
|
||||
const char *display = getenv("DISPLAY");
|
||||
if (display && *display) {
|
||||
struct strbuf man_page = STRBUF_INIT;
|
||||
const char *filename = "kfmclient";
|
||||
|
||||
/* It's simpler to launch konqueror using kfmclient. */
|
||||
if (path) {
|
||||
const char *file = strrchr(path, '/');
|
||||
if (file && !strcmp(file + 1, "konqueror")) {
|
||||
char *new = xstrdup(path);
|
||||
char *dest = strrchr(new, '/');
|
||||
|
||||
/* strlen("konqueror") == strlen("kfmclient") */
|
||||
strcpy(dest + 1, "kfmclient");
|
||||
path = new;
|
||||
}
|
||||
if (file)
|
||||
filename = file;
|
||||
} else
|
||||
path = "kfmclient";
|
||||
strbuf_addf(&man_page, "man:%s(1)", page);
|
||||
execlp(path, filename, "newTab", man_page.buf, NULL);
|
||||
warning("failed to exec '%s': %s", path, strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
static void exec_man_man(const char* path, const char *page)
|
||||
{
|
||||
if (!path)
|
||||
path = "man";
|
||||
execlp(path, "man", page, NULL);
|
||||
warning("failed to exec '%s': %s", path, strerror(errno));
|
||||
}
|
||||
|
||||
static void exec_man_cmd(const char *cmd, const char *page)
|
||||
{
|
||||
struct strbuf shell_cmd = STRBUF_INIT;
|
||||
strbuf_addf(&shell_cmd, "%s %s", cmd, page);
|
||||
execl("/bin/sh", "sh", "-c", shell_cmd.buf, NULL);
|
||||
warning("failed to exec '%s': %s", cmd, strerror(errno));
|
||||
}
|
||||
|
||||
static void add_man_viewer(const char *name)
|
||||
{
|
||||
struct man_viewer_list **p = &man_viewer_list;
|
||||
size_t len = strlen(name);
|
||||
|
||||
while (*p)
|
||||
p = &((*p)->next);
|
||||
*p = xcalloc(1, (sizeof(**p) + len + 1));
|
||||
strncpy((*p)->name, name, len);
|
||||
}
|
||||
|
||||
static int supported_man_viewer(const char *name, size_t len)
|
||||
{
|
||||
return (!strncasecmp("man", name, len) ||
|
||||
!strncasecmp("woman", name, len) ||
|
||||
!strncasecmp("konqueror", name, len));
|
||||
}
|
||||
|
||||
static void do_add_man_viewer_info(const char *name,
|
||||
size_t len,
|
||||
const char *value)
|
||||
{
|
||||
struct man_viewer_info_list *new = xcalloc(1, sizeof(*new) + len + 1);
|
||||
|
||||
strncpy(new->name, name, len);
|
||||
new->info = xstrdup(value);
|
||||
new->next = man_viewer_info_list;
|
||||
man_viewer_info_list = new;
|
||||
}
|
||||
|
||||
static int add_man_viewer_path(const char *name,
|
||||
size_t len,
|
||||
const char *value)
|
||||
{
|
||||
if (supported_man_viewer(name, len))
|
||||
do_add_man_viewer_info(name, len, value);
|
||||
else
|
||||
warning("'%s': path for unsupported man viewer.\n"
|
||||
"Please consider using 'man.<tool>.cmd' instead.",
|
||||
name);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int add_man_viewer_cmd(const char *name,
|
||||
size_t len,
|
||||
const char *value)
|
||||
{
|
||||
if (supported_man_viewer(name, len))
|
||||
warning("'%s': cmd for supported man viewer.\n"
|
||||
"Please consider using 'man.<tool>.path' instead.",
|
||||
name);
|
||||
else
|
||||
do_add_man_viewer_info(name, len, value);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int add_man_viewer_info(const char *var, const char *value)
|
||||
{
|
||||
const char *name = var + 4;
|
||||
const char *subkey = strrchr(name, '.');
|
||||
|
||||
if (!subkey)
|
||||
return error("Config with no key for man viewer: %s", name);
|
||||
|
||||
if (!strcmp(subkey, ".path")) {
|
||||
if (!value)
|
||||
return config_error_nonbool(var);
|
||||
return add_man_viewer_path(name, subkey - name, value);
|
||||
}
|
||||
if (!strcmp(subkey, ".cmd")) {
|
||||
if (!value)
|
||||
return config_error_nonbool(var);
|
||||
return add_man_viewer_cmd(name, subkey - name, value);
|
||||
}
|
||||
|
||||
warning("'%s': unsupported man viewer sub key.", subkey);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int git_help_config(const char *var, const char *value, void *cb)
|
||||
{
|
||||
if (!strcmp(var, "help.format")) {
|
||||
if (!value)
|
||||
return config_error_nonbool(var);
|
||||
help_format = parse_help_format(value);
|
||||
return 0;
|
||||
}
|
||||
if (!strcmp(var, "man.viewer")) {
|
||||
if (!value)
|
||||
return config_error_nonbool(var);
|
||||
add_man_viewer(value);
|
||||
return 0;
|
||||
}
|
||||
if (!prefixcmp(var, "man."))
|
||||
return add_man_viewer_info(var, value);
|
||||
|
||||
return git_default_config(var, value, cb);
|
||||
}
|
||||
|
||||
static struct cmdnames main_cmds, other_cmds;
|
||||
|
||||
void list_common_cmds_help(void)
|
||||
{
|
||||
int i, longest = 0;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
|
||||
if (longest < strlen(common_cmds[i].name))
|
||||
longest = strlen(common_cmds[i].name);
|
||||
}
|
||||
|
||||
puts("The most commonly used git commands are:");
|
||||
for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
|
||||
printf(" %s ", common_cmds[i].name);
|
||||
mput_char(' ', longest - strlen(common_cmds[i].name));
|
||||
puts(common_cmds[i].help);
|
||||
}
|
||||
}
|
||||
|
||||
static int is_git_command(const char *s)
|
||||
{
|
||||
return is_in_cmdlist(&main_cmds, s) ||
|
||||
is_in_cmdlist(&other_cmds, s);
|
||||
}
|
||||
|
||||
static const char *prepend(const char *prefix, const char *cmd)
|
||||
{
|
||||
size_t pre_len = strlen(prefix);
|
||||
size_t cmd_len = strlen(cmd);
|
||||
char *p = xmalloc(pre_len + cmd_len + 1);
|
||||
memcpy(p, prefix, pre_len);
|
||||
strcpy(p + pre_len, cmd);
|
||||
return p;
|
||||
}
|
||||
|
||||
static const char *cmd_to_page(const char *git_cmd)
|
||||
{
|
||||
if (!git_cmd)
|
||||
return "git";
|
||||
else if (!prefixcmp(git_cmd, "git"))
|
||||
return git_cmd;
|
||||
else if (is_git_command(git_cmd))
|
||||
return prepend("git-", git_cmd);
|
||||
else
|
||||
return prepend("git", git_cmd);
|
||||
}
|
||||
|
||||
static void setup_man_path(void)
|
||||
{
|
||||
struct strbuf new_path;
|
||||
const char *old_path = getenv("MANPATH");
|
||||
|
||||
strbuf_init(&new_path, 0);
|
||||
|
||||
/* We should always put ':' after our path. If there is no
|
||||
* old_path, the ':' at the end will let 'man' to try
|
||||
* system-wide paths after ours to find the manual page. If
|
||||
* there is old_path, we need ':' as delimiter. */
|
||||
strbuf_addstr(&new_path, GIT_MAN_PATH);
|
||||
strbuf_addch(&new_path, ':');
|
||||
if (old_path)
|
||||
strbuf_addstr(&new_path, old_path);
|
||||
|
||||
setenv("MANPATH", new_path.buf, 1);
|
||||
|
||||
strbuf_release(&new_path);
|
||||
}
|
||||
|
||||
static void exec_viewer(const char *name, const char *page)
|
||||
{
|
||||
const char *info = get_man_viewer_info(name);
|
||||
|
||||
if (!strcasecmp(name, "man"))
|
||||
exec_man_man(info, page);
|
||||
else if (!strcasecmp(name, "woman"))
|
||||
exec_woman_emacs(info, page);
|
||||
else if (!strcasecmp(name, "konqueror"))
|
||||
exec_man_konqueror(info, page);
|
||||
else if (info)
|
||||
exec_man_cmd(info, page);
|
||||
else
|
||||
warning("'%s': unknown man viewer.", name);
|
||||
}
|
||||
|
||||
static void show_man_page(const char *git_cmd)
|
||||
{
|
||||
struct man_viewer_list *viewer;
|
||||
const char *page = cmd_to_page(git_cmd);
|
||||
const char *fallback = getenv("GIT_MAN_VIEWER");
|
||||
|
||||
setup_man_path();
|
||||
for (viewer = man_viewer_list; viewer; viewer = viewer->next)
|
||||
{
|
||||
exec_viewer(viewer->name, page); /* will return when unable */
|
||||
}
|
||||
if (fallback)
|
||||
exec_viewer(fallback, page);
|
||||
exec_viewer("man", page);
|
||||
die("no man viewer handled the request");
|
||||
}
|
||||
|
||||
static void show_info_page(const char *git_cmd)
|
||||
{
|
||||
const char *page = cmd_to_page(git_cmd);
|
||||
setenv("INFOPATH", GIT_INFO_PATH, 1);
|
||||
execlp("info", "info", "gitman", page, NULL);
|
||||
}
|
||||
|
||||
static void get_html_page_path(struct strbuf *page_path, const char *page)
|
||||
{
|
||||
struct stat st;
|
||||
const char *html_path = system_path(GIT_HTML_PATH);
|
||||
|
||||
/* Check that we have a git documentation directory. */
|
||||
if (stat(mkpath("%s/git.html", html_path), &st)
|
||||
|| !S_ISREG(st.st_mode))
|
||||
die("'%s': not a documentation directory.", html_path);
|
||||
|
||||
strbuf_init(page_path, 0);
|
||||
strbuf_addf(page_path, "%s/%s.html", html_path, page);
|
||||
}
|
||||
|
||||
/*
|
||||
* If open_html is not defined in a platform-specific way (see for
|
||||
* example compat/mingw.h), we use the script web--browse to display
|
||||
* HTML.
|
||||
*/
|
||||
#ifndef open_html
|
||||
void open_html(const char *path)
|
||||
{
|
||||
execl_git_cmd("web--browse", "-c", "help.browser", path, NULL);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void show_html_page(const char *git_cmd)
|
||||
{
|
||||
const char *page = cmd_to_page(git_cmd);
|
||||
struct strbuf page_path; /* it leaks but we exec bellow */
|
||||
|
||||
get_html_page_path(&page_path, page);
|
||||
|
||||
open_html(page_path.buf);
|
||||
}
|
||||
|
||||
int cmd_help(int argc, const char **argv, const char *prefix)
|
||||
{
|
||||
int nongit;
|
||||
const char *alias;
|
||||
load_command_list("git-", &main_cmds, &other_cmds);
|
||||
|
||||
setup_git_directory_gently(&nongit);
|
||||
git_config(git_help_config, NULL);
|
||||
|
||||
argc = parse_options(argc, argv, builtin_help_options,
|
||||
builtin_help_usage, 0);
|
||||
|
||||
if (show_all) {
|
||||
printf("usage: %s\n\n", git_usage_string);
|
||||
list_commands("git commands", &main_cmds, &other_cmds);
|
||||
printf("%s\n", git_more_info_string);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!argv[0]) {
|
||||
printf("usage: %s\n\n", git_usage_string);
|
||||
list_common_cmds_help();
|
||||
printf("\n%s\n", git_more_info_string);
|
||||
return 0;
|
||||
}
|
||||
|
||||
alias = alias_lookup(argv[0]);
|
||||
if (alias && !is_git_command(argv[0])) {
|
||||
printf("`git %s' is aliased to `%s'\n", argv[0], alias);
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (help_format) {
|
||||
case HELP_FORMAT_MAN:
|
||||
show_man_page(argv[0]);
|
||||
break;
|
||||
case HELP_FORMAT_INFO:
|
||||
show_info_page(argv[0]);
|
||||
break;
|
||||
case HELP_FORMAT_WEB:
|
||||
show_html_page(argv[0]);
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -78,7 +78,7 @@ static void show_dir_entry(const char *tag, struct dir_entry *ent)
|
||||
int offset = prefix_offset;
|
||||
|
||||
if (len >= ent->len)
|
||||
die("git-ls-files: internal error - directory entry not superset of prefix");
|
||||
die("git ls-files: internal error - directory entry not superset of prefix");
|
||||
|
||||
if (pathspec && !pathspec_match(pathspec, ps_matched, ent->name, len))
|
||||
return;
|
||||
@@ -183,7 +183,7 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce)
|
||||
int offset = prefix_offset;
|
||||
|
||||
if (len >= ce_namelen(ce))
|
||||
die("git-ls-files: internal error - cache entry not superset of prefix");
|
||||
die("git ls-files: internal error - cache entry not superset of prefix");
|
||||
|
||||
if (pathspec && !pathspec_match(pathspec, ps_matched, ce->name, len))
|
||||
return;
|
||||
@@ -319,7 +319,7 @@ static const char *verify_pathspec(const char *prefix)
|
||||
}
|
||||
|
||||
if (prefix_offset > max || memcmp(prev, prefix, prefix_offset))
|
||||
die("git-ls-files: cannot generate relative filenames containing '..'");
|
||||
die("git ls-files: cannot generate relative filenames containing '..'");
|
||||
|
||||
prefix_len = max;
|
||||
return max ? xmemdupz(prev, max) : NULL;
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "log-tree.h"
|
||||
#include "color.h"
|
||||
#include "rerere.h"
|
||||
#include "help.h"
|
||||
|
||||
#define DEFAULT_TWOHEAD (1<<0)
|
||||
#define DEFAULT_OCTOPUS (1<<1)
|
||||
@@ -77,7 +78,9 @@ static int option_parse_message(const struct option *opt,
|
||||
static struct strategy *get_strategy(const char *name)
|
||||
{
|
||||
int i;
|
||||
struct strbuf err;
|
||||
struct strategy *ret;
|
||||
static struct cmdnames main_cmds, other_cmds;
|
||||
static int loaded;
|
||||
|
||||
if (!name)
|
||||
return NULL;
|
||||
@@ -86,12 +89,43 @@ static struct strategy *get_strategy(const char *name)
|
||||
if (!strcmp(name, all_strategy[i].name))
|
||||
return &all_strategy[i];
|
||||
|
||||
strbuf_init(&err, 0);
|
||||
for (i = 0; i < ARRAY_SIZE(all_strategy); i++)
|
||||
strbuf_addf(&err, " %s", all_strategy[i].name);
|
||||
fprintf(stderr, "Could not find merge strategy '%s'.\n", name);
|
||||
fprintf(stderr, "Available strategies are:%s.\n", err.buf);
|
||||
exit(1);
|
||||
if (!loaded) {
|
||||
struct cmdnames not_strategies;
|
||||
loaded = 1;
|
||||
|
||||
memset(¬_strategies, 0, sizeof(struct cmdnames));
|
||||
load_command_list("git-merge-", &main_cmds, &other_cmds);
|
||||
for (i = 0; i < main_cmds.cnt; i++) {
|
||||
int j, found = 0;
|
||||
struct cmdname *ent = main_cmds.names[i];
|
||||
for (j = 0; j < ARRAY_SIZE(all_strategy); j++)
|
||||
if (!strncmp(ent->name, all_strategy[j].name, ent->len)
|
||||
&& !all_strategy[j].name[ent->len])
|
||||
found = 1;
|
||||
if (!found)
|
||||
add_cmdname(¬_strategies, ent->name, ent->len);
|
||||
exclude_cmds(&main_cmds, ¬_strategies);
|
||||
}
|
||||
}
|
||||
if (!is_in_cmdlist(&main_cmds, name) && !is_in_cmdlist(&other_cmds, name)) {
|
||||
fprintf(stderr, "Could not find merge strategy '%s'.\n", name);
|
||||
fprintf(stderr, "Available strategies are:");
|
||||
for (i = 0; i < main_cmds.cnt; i++)
|
||||
fprintf(stderr, " %s", main_cmds.names[i]->name);
|
||||
fprintf(stderr, ".\n");
|
||||
if (other_cmds.cnt) {
|
||||
fprintf(stderr, "Available custom strategies are:");
|
||||
for (i = 0; i < other_cmds.cnt; i++)
|
||||
fprintf(stderr, " %s", other_cmds.names[i]->name);
|
||||
fprintf(stderr, ".\n");
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
|
||||
ret = xmalloc(sizeof(struct strategy));
|
||||
memset(ret, 0, sizeof(struct strategy));
|
||||
ret->name = xstrdup(name);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void append_strategy(struct strategy *s)
|
||||
|
||||
@@ -410,25 +410,22 @@ static unsigned long write_object(struct sha1file *f,
|
||||
return hdrlen + datalen;
|
||||
}
|
||||
|
||||
static off_t write_one(struct sha1file *f,
|
||||
static int write_one(struct sha1file *f,
|
||||
struct object_entry *e,
|
||||
off_t offset)
|
||||
off_t *offset)
|
||||
{
|
||||
unsigned long size;
|
||||
|
||||
/* offset is non zero if object is written already. */
|
||||
if (e->idx.offset || e->preferred_base)
|
||||
return offset;
|
||||
return 1;
|
||||
|
||||
/* if we are deltified, write out base object first. */
|
||||
if (e->delta) {
|
||||
offset = write_one(f, e->delta, offset);
|
||||
if (!offset)
|
||||
return 0;
|
||||
}
|
||||
if (e->delta && !write_one(f, e->delta, offset))
|
||||
return 0;
|
||||
|
||||
e->idx.offset = offset;
|
||||
size = write_object(f, e, offset);
|
||||
e->idx.offset = *offset;
|
||||
size = write_object(f, e, *offset);
|
||||
if (!size) {
|
||||
e->idx.offset = 0;
|
||||
return 0;
|
||||
@@ -436,9 +433,10 @@ static off_t write_one(struct sha1file *f,
|
||||
written_list[nr_written++] = &e->idx;
|
||||
|
||||
/* make sure off_t is sufficiently large not to wrap */
|
||||
if (offset > offset + size)
|
||||
if (*offset > *offset + size)
|
||||
die("pack too large for current definition of off_t");
|
||||
return offset + size;
|
||||
*offset += size;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* forward declaration for write_pack_file */
|
||||
@@ -448,7 +446,7 @@ static void write_pack_file(void)
|
||||
{
|
||||
uint32_t i = 0, j;
|
||||
struct sha1file *f;
|
||||
off_t offset, offset_one, last_obj_offset = 0;
|
||||
off_t offset;
|
||||
struct pack_header hdr;
|
||||
uint32_t nr_remaining = nr_result;
|
||||
time_t last_mtime = 0;
|
||||
@@ -480,11 +478,8 @@ static void write_pack_file(void)
|
||||
offset = sizeof(hdr);
|
||||
nr_written = 0;
|
||||
for (; i < nr_objects; i++) {
|
||||
last_obj_offset = offset;
|
||||
offset_one = write_one(f, objects + i, offset);
|
||||
if (!offset_one)
|
||||
if (!write_one(f, objects + i, &offset))
|
||||
break;
|
||||
offset = offset_one;
|
||||
display_progress(progress_state, written);
|
||||
}
|
||||
|
||||
@@ -497,9 +492,9 @@ static void write_pack_file(void)
|
||||
} else if (nr_written == nr_remaining) {
|
||||
sha1close(f, sha1, CSUM_FSYNC);
|
||||
} else {
|
||||
int fd = sha1close(f, NULL, 0);
|
||||
fixup_pack_header_footer(fd, sha1, pack_tmp_name, nr_written);
|
||||
fsync_or_die(fd, pack_tmp_name);
|
||||
int fd = sha1close(f, sha1, 0);
|
||||
fixup_pack_header_footer(fd, sha1, pack_tmp_name,
|
||||
nr_written, sha1, offset);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
@@ -1096,9 +1091,12 @@ static void check_object(struct object_entry *entry)
|
||||
}
|
||||
|
||||
entry->type = sha1_object_info(entry->idx.sha1, &entry->size);
|
||||
if (entry->type < 0)
|
||||
die("unable to get type of object %s",
|
||||
sha1_to_hex(entry->idx.sha1));
|
||||
/*
|
||||
* The error condition is checked in prepare_pack(). This is
|
||||
* to permit a missing preferred base object to be ignored
|
||||
* as a preferred base. Doing so can result in a larger
|
||||
* pack file, but the transfer will still take place.
|
||||
*/
|
||||
}
|
||||
|
||||
static int pack_offset_sort(const void *_a, const void *_b)
|
||||
@@ -1722,8 +1720,12 @@ static void prepare_pack(int window, int depth)
|
||||
if (entry->no_try_delta)
|
||||
continue;
|
||||
|
||||
if (!entry->preferred_base)
|
||||
if (!entry->preferred_base) {
|
||||
nr_deltas++;
|
||||
if (entry->type < 0)
|
||||
die("unable to get type of object %s",
|
||||
sha1_to_hex(entry->idx.sha1));
|
||||
}
|
||||
|
||||
delta_list[n++] = entry;
|
||||
}
|
||||
|
||||
@@ -194,6 +194,8 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
|
||||
usage(read_tree_usage);
|
||||
if ((opts.dir && !opts.update))
|
||||
die("--exclude-per-directory is meaningless unless -u");
|
||||
if (opts.merge && !opts.index_only)
|
||||
setup_work_tree();
|
||||
|
||||
if (opts.merge) {
|
||||
if (stage < 2)
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "cache-tree.h"
|
||||
#include "diff.h"
|
||||
#include "revision.h"
|
||||
#include "rerere.h"
|
||||
|
||||
/*
|
||||
* This implements the builtins revert and cherry-pick.
|
||||
@@ -395,6 +396,7 @@ static int revert_or_cherry_pick(int argc, const char **argv)
|
||||
die ("Error wrapping up %s", defmsg);
|
||||
fprintf(stderr, "Automatic %s failed.%s\n",
|
||||
me, help_msg(commit->object.sha1));
|
||||
rerere();
|
||||
exit(1);
|
||||
}
|
||||
if (commit_lock_file(&msg_file) < 0)
|
||||
|
||||
@@ -221,7 +221,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
|
||||
printf("rm '%s'\n", path);
|
||||
|
||||
if (remove_file_from_cache(path))
|
||||
die("git-rm: unable to remove %s", path);
|
||||
die("git rm: unable to remove %s", path);
|
||||
}
|
||||
|
||||
if (show_only)
|
||||
@@ -244,7 +244,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
|
||||
continue;
|
||||
}
|
||||
if (!removed)
|
||||
die("git-rm: %s: %s", path, strerror(errno));
|
||||
die("git rm: %s: %s", path, strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ match:
|
||||
* ref points at a nonexistent object.
|
||||
*/
|
||||
if (!has_sha1_file(sha1))
|
||||
die("git-show-ref: bad ref %s (%s)", refname,
|
||||
die("git show-ref: bad ref %s (%s)", refname,
|
||||
sha1_to_hex(sha1));
|
||||
|
||||
if (quiet)
|
||||
@@ -82,12 +82,12 @@ match:
|
||||
else {
|
||||
obj = parse_object(sha1);
|
||||
if (!obj)
|
||||
die("git-show-ref: bad ref %s (%s)", refname,
|
||||
die("git show-ref: bad ref %s (%s)", refname,
|
||||
sha1_to_hex(sha1));
|
||||
if (obj->type == OBJ_TAG) {
|
||||
obj = deref_tag(obj, refname, 0);
|
||||
if (!obj)
|
||||
die("git-show-ref: bad tag at ref %s (%s)", refname,
|
||||
die("git show-ref: bad tag at ref %s (%s)", refname,
|
||||
sha1_to_hex(sha1));
|
||||
hex = find_unique_abbrev(obj->sha1, abbrev);
|
||||
printf("%s %s^{}\n", hex, refname);
|
||||
|
||||
@@ -76,7 +76,7 @@ int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix)
|
||||
|
||||
n = read_in_full(0, buffer, HEADERSIZE);
|
||||
if (n < HEADERSIZE)
|
||||
die("git-get-tar-commit-id: read error");
|
||||
die("git get-tar-commit-id: read error");
|
||||
if (header->typeflag[0] != 'g')
|
||||
return 1;
|
||||
if (memcmp(content, "52 comment=", 11))
|
||||
@@ -84,7 +84,7 @@ int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix)
|
||||
|
||||
n = write_in_full(1, content + 11, 41);
|
||||
if (n < 41)
|
||||
die("git-get-tar-commit-id: write error");
|
||||
die("git get-tar-commit-id: write error");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -265,7 +265,7 @@ static void chmod_path(int flip, const char *path)
|
||||
report("chmod %cx '%s'", flip, path);
|
||||
return;
|
||||
fail:
|
||||
die("git-update-index: cannot chmod %cx '%s'", flip, path);
|
||||
die("git update-index: cannot chmod %cx '%s'", flip, path);
|
||||
}
|
||||
|
||||
static void update_one(const char *path, const char *prefix, int prefix_length)
|
||||
@@ -283,7 +283,7 @@ static void update_one(const char *path, const char *prefix, int prefix_length)
|
||||
|
||||
if (force_remove) {
|
||||
if (remove_file_from_cache(p))
|
||||
die("git-update-index: unable to remove %s", path);
|
||||
die("git update-index: unable to remove %s", path);
|
||||
report("remove '%s'", path);
|
||||
goto free_return;
|
||||
}
|
||||
@@ -354,7 +354,7 @@ static void read_index_info(int line_termination)
|
||||
if (line_termination && path_name[0] == '"') {
|
||||
strbuf_reset(&uq);
|
||||
if (unquote_c_style(&uq, path_name, NULL)) {
|
||||
die("git-update-index: bad quoting of path name");
|
||||
die("git update-index: bad quoting of path name");
|
||||
}
|
||||
path_name = uq.buf;
|
||||
}
|
||||
@@ -367,7 +367,7 @@ static void read_index_info(int line_termination)
|
||||
if (!mode) {
|
||||
/* mode == 0 means there is no such path -- remove */
|
||||
if (remove_file_from_cache(path_name))
|
||||
die("git-update-index: unable to remove %s",
|
||||
die("git update-index: unable to remove %s",
|
||||
ptr);
|
||||
}
|
||||
else {
|
||||
@@ -377,7 +377,7 @@ static void read_index_info(int line_termination)
|
||||
*/
|
||||
ptr[-42] = ptr[-1] = 0;
|
||||
if (add_cacheinfo(mode, sha1, path_name, stage))
|
||||
die("git-update-index: unable to update %s",
|
||||
die("git update-index: unable to update %s",
|
||||
path_name);
|
||||
}
|
||||
continue;
|
||||
@@ -617,10 +617,12 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(path, "--refresh")) {
|
||||
setup_work_tree();
|
||||
has_errors |= refresh_cache(refresh_flags);
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(path, "--really-refresh")) {
|
||||
setup_work_tree();
|
||||
has_errors |= refresh_cache(REFRESH_REALLY | refresh_flags);
|
||||
continue;
|
||||
}
|
||||
@@ -629,12 +631,12 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
|
||||
unsigned int mode;
|
||||
|
||||
if (i+3 >= argc)
|
||||
die("git-update-index: --cacheinfo <mode> <sha1> <path>");
|
||||
die("git update-index: --cacheinfo <mode> <sha1> <path>");
|
||||
|
||||
if (strtoul_ui(argv[i+1], 8, &mode) ||
|
||||
get_sha1_hex(argv[i+2], sha1) ||
|
||||
add_cacheinfo(mode, sha1, argv[i+3], 0))
|
||||
die("git-update-index: --cacheinfo"
|
||||
die("git update-index: --cacheinfo"
|
||||
" cannot add %s", argv[i+3]);
|
||||
i += 3;
|
||||
continue;
|
||||
@@ -642,7 +644,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
|
||||
if (!strcmp(path, "--chmod=-x") ||
|
||||
!strcmp(path, "--chmod=+x")) {
|
||||
if (argc <= i+1)
|
||||
die("git-update-index: %s <path>", path);
|
||||
die("git update-index: %s <path>", path);
|
||||
set_executable_bit = path[8];
|
||||
continue;
|
||||
}
|
||||
@@ -687,6 +689,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
|
||||
goto finish;
|
||||
}
|
||||
if (!strcmp(path, "--again") || !strcmp(path, "-g")) {
|
||||
setup_work_tree();
|
||||
has_errors = do_reupdate(argc - i, argv + i,
|
||||
prefix, prefix_length);
|
||||
if (has_errors)
|
||||
@@ -705,6 +708,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
|
||||
usage(update_index_usage);
|
||||
die("unknown option %s", path);
|
||||
}
|
||||
setup_work_tree();
|
||||
p = prefix_path(prefix, prefix_length, path);
|
||||
update_one(p, NULL, 0);
|
||||
if (set_executable_bit)
|
||||
@@ -717,6 +721,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
|
||||
|
||||
strbuf_init(&buf, 0);
|
||||
strbuf_init(&nbuf, 0);
|
||||
setup_work_tree();
|
||||
while (strbuf_getline(&buf, stdin, line_termination) != EOF) {
|
||||
const char *p;
|
||||
if (line_termination && buf.buf[0] == '"') {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "builtin.h"
|
||||
#include "cache.h"
|
||||
#include "pack.h"
|
||||
|
||||
#include "pack-revindex.h"
|
||||
|
||||
#define MAX_CHAIN 50
|
||||
|
||||
@@ -129,6 +129,7 @@ int cmd_verify_pack(int argc, const char **argv, const char *prefix)
|
||||
else {
|
||||
if (verify_one_pack(argv[1], verbose))
|
||||
err = 1;
|
||||
discard_revindex();
|
||||
nothing_done = 0;
|
||||
}
|
||||
argc--; argv++;
|
||||
|
||||
1
cache.h
1
cache.h
@@ -379,6 +379,7 @@ extern int remove_file_from_index(struct index_state *, const char *path);
|
||||
#define ADD_CACHE_VERBOSE 1
|
||||
#define ADD_CACHE_PRETEND 2
|
||||
#define ADD_CACHE_IGNORE_ERRORS 4
|
||||
#define ADD_CACHE_IGNORE_REMOVAL 8
|
||||
extern int add_to_index(struct index_state *, const char *path, struct stat *, int flags);
|
||||
extern int add_file_to_index(struct index_state *, const char *path, int flags);
|
||||
extern struct cache_entry *make_cache_entry(unsigned int mode, const unsigned char *sha1, const char *path, int stage, int refresh);
|
||||
|
||||
3
commit.h
3
commit.h
@@ -67,7 +67,8 @@ extern int non_ascii(int);
|
||||
struct rev_info; /* in revision.h, it circularly uses enum cmit_fmt */
|
||||
extern void get_commit_format(const char *arg, struct rev_info *);
|
||||
extern void format_commit_message(const struct commit *commit,
|
||||
const void *format, struct strbuf *sb);
|
||||
const void *format, struct strbuf *sb,
|
||||
enum date_mode dmode);
|
||||
extern void pretty_print_commit(enum cmit_fmt fmt, const struct commit*,
|
||||
struct strbuf *,
|
||||
int abbrev, const char *subject,
|
||||
|
||||
@@ -97,7 +97,7 @@ int get_ack(int fd, unsigned char *result_sha1)
|
||||
int len = packet_read_line(fd, line, sizeof(line));
|
||||
|
||||
if (!len)
|
||||
die("git-fetch-pack: expected ACK/NAK, got EOF");
|
||||
die("git fetch-pack: expected ACK/NAK, got EOF");
|
||||
if (line[len-1] == '\n')
|
||||
line[--len] = 0;
|
||||
if (!strcmp(line, "NAK"))
|
||||
@@ -109,7 +109,7 @@ int get_ack(int fd, unsigned char *result_sha1)
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
die("git-fetch_pack: expected ACK/NAK, got '%s'", line);
|
||||
die("git fetch_pack: expected ACK/NAK, got '%s'", line);
|
||||
}
|
||||
|
||||
int path_match(const char *path, int nr, char **match)
|
||||
|
||||
@@ -386,7 +386,9 @@ __git_porcelain_commands ()
|
||||
cat-file) : plumbing;;
|
||||
check-attr) : plumbing;;
|
||||
check-ref-format) : plumbing;;
|
||||
checkout-index) : plumbing;;
|
||||
commit-tree) : plumbing;;
|
||||
count-objects) : infrequent;;
|
||||
cvsexportcommit) : export;;
|
||||
cvsimport) : import;;
|
||||
cvsserver) : daemon;;
|
||||
@@ -395,6 +397,7 @@ __git_porcelain_commands ()
|
||||
diff-index) : plumbing;;
|
||||
diff-tree) : plumbing;;
|
||||
fast-import) : import;;
|
||||
fast-export) : export;;
|
||||
fsck-objects) : plumbing;;
|
||||
fetch-pack) : plumbing;;
|
||||
fmt-merge-msg) : plumbing;;
|
||||
@@ -404,6 +407,10 @@ __git_porcelain_commands ()
|
||||
index-pack) : plumbing;;
|
||||
init-db) : deprecated;;
|
||||
local-fetch) : plumbing;;
|
||||
lost-found) : infrequent;;
|
||||
ls-files) : plumbing;;
|
||||
ls-remote) : plumbing;;
|
||||
ls-tree) : plumbing;;
|
||||
mailinfo) : plumbing;;
|
||||
mailsplit) : plumbing;;
|
||||
merge-*) : plumbing;;
|
||||
@@ -428,6 +435,7 @@ __git_porcelain_commands ()
|
||||
runstatus) : plumbing;;
|
||||
sh-setup) : internal;;
|
||||
shell) : daemon;;
|
||||
show-ref) : plumbing;;
|
||||
send-pack) : plumbing;;
|
||||
show-index) : plumbing;;
|
||||
ssh-*) : transport;;
|
||||
@@ -442,6 +450,8 @@ __git_porcelain_commands ()
|
||||
upload-archive) : plumbing;;
|
||||
upload-pack) : plumbing;;
|
||||
write-tree) : plumbing;;
|
||||
var) : infrequent;;
|
||||
verify-pack) : infrequent;;
|
||||
verify-tag) : plumbing;;
|
||||
*) echo $i;;
|
||||
esac
|
||||
@@ -1483,7 +1493,7 @@ _git_submodule ()
|
||||
{
|
||||
__git_has_doubledash && return
|
||||
|
||||
local subcommands="add status init update"
|
||||
local subcommands="add status init update summary foreach sync"
|
||||
if [ -z "$(__git_find_subcommand "$subcommands")" ]; then
|
||||
local cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
case "$cur" in
|
||||
|
||||
@@ -708,6 +708,7 @@ class P4Submit(Command):
|
||||
newdiff = newdiff.replace("\n", "\r\n")
|
||||
tmpFile.write(submitTemplate + separatorLine + diff + newdiff)
|
||||
tmpFile.close()
|
||||
mtime = os.stat(fileName).st_mtime
|
||||
defaultEditor = "vi"
|
||||
if platform.system() == "Windows":
|
||||
defaultEditor = "notepad"
|
||||
@@ -716,15 +717,29 @@ class P4Submit(Command):
|
||||
else:
|
||||
editor = os.environ.get("EDITOR", defaultEditor);
|
||||
system(editor + " " + fileName)
|
||||
tmpFile = open(fileName, "rb")
|
||||
message = tmpFile.read()
|
||||
tmpFile.close()
|
||||
os.remove(fileName)
|
||||
submitTemplate = message[:message.index(separatorLine)]
|
||||
if self.isWindows:
|
||||
submitTemplate = submitTemplate.replace("\r\n", "\n")
|
||||
|
||||
p4_write_pipe("submit -i", submitTemplate)
|
||||
response = "y"
|
||||
if os.stat(fileName).st_mtime <= mtime:
|
||||
response = "x"
|
||||
while response != "y" and response != "n":
|
||||
response = raw_input("Submit template unchanged. Submit anyway? [y]es, [n]o (skip this patch) ")
|
||||
|
||||
if response == "y":
|
||||
tmpFile = open(fileName, "rb")
|
||||
message = tmpFile.read()
|
||||
tmpFile.close()
|
||||
submitTemplate = message[:message.index(separatorLine)]
|
||||
if self.isWindows:
|
||||
submitTemplate = submitTemplate.replace("\r\n", "\n")
|
||||
p4_write_pipe("submit -i", submitTemplate)
|
||||
else:
|
||||
for f in editedFiles:
|
||||
p4_system("revert \"%s\"" % f);
|
||||
for f in filesToAdd:
|
||||
p4_system("revert \"%s\"" % f);
|
||||
system("rm %s" %f)
|
||||
|
||||
os.remove(fileName)
|
||||
else:
|
||||
fileName = "submit.txt"
|
||||
file = open(fileName, "w+")
|
||||
@@ -1733,8 +1748,12 @@ class P4Clone(P4Sync):
|
||||
if not P4Sync.run(self, depotPaths):
|
||||
return False
|
||||
if self.branch != "master":
|
||||
if gitBranchExists("refs/remotes/p4/master"):
|
||||
system("git branch master refs/remotes/p4/master")
|
||||
if self.importIntoRemotes:
|
||||
masterbranch = "refs/remotes/p4/master"
|
||||
else:
|
||||
masterbranch = "refs/heads/p4/master"
|
||||
if gitBranchExists(masterbranch):
|
||||
system("git branch master %s" % masterbranch)
|
||||
system("git checkout -f")
|
||||
else:
|
||||
print "Could not detect main branch. No checkout/master branch created."
|
||||
|
||||
@@ -42,11 +42,11 @@ int sha1close(struct sha1file *f, unsigned char *result, unsigned int flags)
|
||||
sha1flush(f, offset);
|
||||
f->offset = 0;
|
||||
}
|
||||
SHA1_Final(f->buffer, &f->ctx);
|
||||
if (result)
|
||||
hashcpy(result, f->buffer);
|
||||
if (flags & (CSUM_CLOSE | CSUM_FSYNC)) {
|
||||
/* write checksum and close fd */
|
||||
SHA1_Final(f->buffer, &f->ctx);
|
||||
if (result)
|
||||
hashcpy(result, f->buffer);
|
||||
sha1flush(f, 20);
|
||||
if (flags & CSUM_FSYNC)
|
||||
fsync_or_die(f->fd, f->name);
|
||||
|
||||
15
ctype.c
15
ctype.c
@@ -5,17 +5,24 @@
|
||||
*/
|
||||
#include "cache.h"
|
||||
|
||||
/* Just so that no insane platform contaminate namespace with these symbols */
|
||||
#undef SS
|
||||
#undef AA
|
||||
#undef DD
|
||||
#undef GS
|
||||
|
||||
#define SS GIT_SPACE
|
||||
#define AA GIT_ALPHA
|
||||
#define DD GIT_DIGIT
|
||||
#define GS GIT_SPECIAL /* \0, *, ?, [, \\ */
|
||||
|
||||
unsigned char sane_ctype[256] = {
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, SS, SS, 0, 0, SS, 0, 0, /* 0-15 */
|
||||
GS, 0, 0, 0, 0, 0, 0, 0, 0, SS, SS, 0, 0, SS, 0, 0, /* 0-15 */
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 16-15 */
|
||||
SS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 32-15 */
|
||||
DD, DD, DD, DD, DD, DD, DD, DD, DD, DD, 0, 0, 0, 0, 0, 0, /* 48-15 */
|
||||
SS, 0, 0, 0, 0, 0, 0, 0, 0, 0, GS, 0, 0, 0, 0, 0, /* 32-15 */
|
||||
DD, DD, DD, DD, DD, DD, DD, DD, DD, DD, 0, 0, 0, 0, 0, GS, /* 48-15 */
|
||||
0, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, /* 64-15 */
|
||||
AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, 0, 0, 0, 0, 0, /* 80-15 */
|
||||
AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, GS, GS, 0, 0, 0, /* 80-15 */
|
||||
0, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, /* 96-15 */
|
||||
AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, 0, 0, 0, 0, 0, /* 112-15 */
|
||||
/* Nothing in the 128.. range */
|
||||
|
||||
303
daemon.c
303
daemon.c
@@ -16,12 +16,11 @@
|
||||
static int log_syslog;
|
||||
static int verbose;
|
||||
static int reuseaddr;
|
||||
static int child_handler_pipe[2];
|
||||
|
||||
static const char daemon_usage[] =
|
||||
"git daemon [--verbose] [--syslog] [--export-all]\n"
|
||||
" [--timeout=n] [--init-timeout=n] [--strict-paths]\n"
|
||||
" [--base-path=path] [--base-path-relaxed]\n"
|
||||
" [--timeout=n] [--init-timeout=n] [--max-connections=n]\n"
|
||||
" [--strict-paths] [--base-path=path] [--base-path-relaxed]\n"
|
||||
" [--user-path | --user-path=path]\n"
|
||||
" [--interpolated-path=path]\n"
|
||||
" [--reuseaddr] [--detach] [--pid-file=file]\n"
|
||||
@@ -78,38 +77,19 @@ static struct interp interp_table[] = {
|
||||
|
||||
static void logreport(int priority, const char *err, va_list params)
|
||||
{
|
||||
/* We should do a single write so that it is atomic and output
|
||||
* of several processes do not get intermingled. */
|
||||
char buf[1024];
|
||||
int buflen;
|
||||
int maxlen, msglen;
|
||||
|
||||
/* sizeof(buf) should be big enough for "[pid] \n" */
|
||||
buflen = snprintf(buf, sizeof(buf), "[%ld] ", (long) getpid());
|
||||
|
||||
maxlen = sizeof(buf) - buflen - 1; /* -1 for our own LF */
|
||||
msglen = vsnprintf(buf + buflen, maxlen, err, params);
|
||||
|
||||
if (log_syslog) {
|
||||
char buf[1024];
|
||||
vsnprintf(buf, sizeof(buf), err, params);
|
||||
syslog(priority, "%s", buf);
|
||||
return;
|
||||
} else {
|
||||
/*
|
||||
* Since stderr is set to linebuffered mode, the
|
||||
* logging of different processes will not overlap
|
||||
*/
|
||||
fprintf(stderr, "[%"PRIuMAX"] ", (uintmax_t)getpid());
|
||||
vfprintf(stderr, err, params);
|
||||
fputc('\n', stderr);
|
||||
}
|
||||
|
||||
/* maxlen counted our own LF but also counts space given to
|
||||
* vsnprintf for the terminating NUL. We want to make sure that
|
||||
* we have space for our own LF and NUL after the "meat" of the
|
||||
* message, so truncate it at maxlen - 1.
|
||||
*/
|
||||
if (msglen > maxlen - 1)
|
||||
msglen = maxlen - 1;
|
||||
else if (msglen < 0)
|
||||
msglen = 0; /* Protect against weird return values. */
|
||||
buflen += msglen;
|
||||
|
||||
buf[buflen++] = '\n';
|
||||
buf[buflen] = '\0';
|
||||
|
||||
write_in_full(2, buf, buflen);
|
||||
}
|
||||
|
||||
static void logerror(const char *err, ...)
|
||||
@@ -604,169 +584,107 @@ static int execute(struct sockaddr *addr)
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int max_connections = 32;
|
||||
|
||||
/*
|
||||
* We count spawned/reaped separately, just to avoid any
|
||||
* races when updating them from signals. The SIGCHLD handler
|
||||
* will only update children_reaped, and the fork logic will
|
||||
* only update children_spawned.
|
||||
*
|
||||
* MAX_CHILDREN should be a power-of-two to make the modulus
|
||||
* operation cheap. It should also be at least twice
|
||||
* the maximum number of connections we will ever allow.
|
||||
*/
|
||||
#define MAX_CHILDREN 128
|
||||
|
||||
static int max_connections = 25;
|
||||
|
||||
/* These are updated by the signal handler */
|
||||
static volatile unsigned int children_reaped;
|
||||
static pid_t dead_child[MAX_CHILDREN];
|
||||
|
||||
/* These are updated by the main loop */
|
||||
static unsigned int children_spawned;
|
||||
static unsigned int children_deleted;
|
||||
static unsigned int live_children;
|
||||
|
||||
static struct child {
|
||||
struct child *next;
|
||||
pid_t pid;
|
||||
int addrlen;
|
||||
struct sockaddr_storage address;
|
||||
} live_child[MAX_CHILDREN];
|
||||
} *firstborn;
|
||||
|
||||
static void add_child(int idx, pid_t pid, struct sockaddr *addr, int addrlen)
|
||||
static void add_child(pid_t pid, struct sockaddr *addr, int addrlen)
|
||||
{
|
||||
live_child[idx].pid = pid;
|
||||
live_child[idx].addrlen = addrlen;
|
||||
memcpy(&live_child[idx].address, addr, addrlen);
|
||||
struct child *newborn, **cradle;
|
||||
|
||||
/*
|
||||
* This must be xcalloc() -- we'll compare the whole sockaddr_storage
|
||||
* but individual address may be shorter.
|
||||
*/
|
||||
newborn = xcalloc(1, sizeof(*newborn));
|
||||
live_children++;
|
||||
newborn->pid = pid;
|
||||
memcpy(&newborn->address, addr, addrlen);
|
||||
for (cradle = &firstborn; *cradle; cradle = &(*cradle)->next)
|
||||
if (!memcmp(&(*cradle)->address, &newborn->address,
|
||||
sizeof(newborn->address)))
|
||||
break;
|
||||
newborn->next = *cradle;
|
||||
*cradle = newborn;
|
||||
}
|
||||
|
||||
/*
|
||||
* Walk from "deleted" to "spawned", and remove child "pid".
|
||||
*
|
||||
* We move everything up by one, since the new "deleted" will
|
||||
* be one higher.
|
||||
*/
|
||||
static void remove_child(pid_t pid, unsigned deleted, unsigned spawned)
|
||||
static void remove_child(pid_t pid)
|
||||
{
|
||||
struct child n;
|
||||
struct child **cradle, *blanket;
|
||||
|
||||
deleted %= MAX_CHILDREN;
|
||||
spawned %= MAX_CHILDREN;
|
||||
if (live_child[deleted].pid == pid) {
|
||||
live_child[deleted].pid = -1;
|
||||
return;
|
||||
}
|
||||
n = live_child[deleted];
|
||||
for (;;) {
|
||||
struct child m;
|
||||
deleted = (deleted + 1) % MAX_CHILDREN;
|
||||
if (deleted == spawned)
|
||||
die("could not find dead child %d\n", pid);
|
||||
m = live_child[deleted];
|
||||
live_child[deleted] = n;
|
||||
if (m.pid == pid)
|
||||
return;
|
||||
n = m;
|
||||
}
|
||||
for (cradle = &firstborn; (blanket = *cradle); cradle = &blanket->next)
|
||||
if (blanket->pid == pid) {
|
||||
*cradle = blanket->next;
|
||||
live_children--;
|
||||
free(blanket);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This gets called if the number of connections grows
|
||||
* past "max_connections".
|
||||
*
|
||||
* We _should_ start off by searching for connections
|
||||
* from the same IP, and if there is some address wth
|
||||
* multiple connections, we should kill that first.
|
||||
*
|
||||
* As it is, we just "randomly" kill 25% of the connections,
|
||||
* and our pseudo-random generator sucks too. I have no
|
||||
* shame.
|
||||
*
|
||||
* Really, this is just a place-holder for a _real_ algorithm.
|
||||
* We kill the newest connection from a duplicate IP.
|
||||
*/
|
||||
static void kill_some_children(int signo, unsigned start, unsigned stop)
|
||||
static void kill_some_child(void)
|
||||
{
|
||||
start %= MAX_CHILDREN;
|
||||
stop %= MAX_CHILDREN;
|
||||
while (start != stop) {
|
||||
if (!(start & 3))
|
||||
kill(live_child[start].pid, signo);
|
||||
start = (start + 1) % MAX_CHILDREN;
|
||||
}
|
||||
const struct child *blanket, *next;
|
||||
|
||||
if (!(blanket = firstborn))
|
||||
return;
|
||||
|
||||
for (; (next = blanket->next); blanket = next)
|
||||
if (!memcmp(&blanket->address, &next->address,
|
||||
sizeof(next->address))) {
|
||||
kill(blanket->pid, SIGTERM);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void check_dead_children(void)
|
||||
{
|
||||
unsigned spawned, reaped, deleted;
|
||||
int status;
|
||||
pid_t pid;
|
||||
|
||||
spawned = children_spawned;
|
||||
reaped = children_reaped;
|
||||
deleted = children_deleted;
|
||||
|
||||
while (deleted < reaped) {
|
||||
pid_t pid = dead_child[deleted % MAX_CHILDREN];
|
||||
const char *dead = pid < 0 ? " (with error)" : "";
|
||||
|
||||
if (pid < 0)
|
||||
pid = -pid;
|
||||
|
||||
/* XXX: Custom logging, since we don't wanna getpid() */
|
||||
if (verbose) {
|
||||
if (log_syslog)
|
||||
syslog(LOG_INFO, "[%d] Disconnected%s",
|
||||
pid, dead);
|
||||
else
|
||||
fprintf(stderr, "[%d] Disconnected%s\n",
|
||||
pid, dead);
|
||||
}
|
||||
remove_child(pid, deleted, spawned);
|
||||
deleted++;
|
||||
}
|
||||
children_deleted = deleted;
|
||||
}
|
||||
|
||||
static void check_max_connections(void)
|
||||
{
|
||||
for (;;) {
|
||||
int active;
|
||||
unsigned spawned, deleted;
|
||||
|
||||
check_dead_children();
|
||||
|
||||
spawned = children_spawned;
|
||||
deleted = children_deleted;
|
||||
|
||||
active = spawned - deleted;
|
||||
if (active <= max_connections)
|
||||
break;
|
||||
|
||||
/* Kill some unstarted connections with SIGTERM */
|
||||
kill_some_children(SIGTERM, deleted, spawned);
|
||||
if (active <= max_connections << 1)
|
||||
break;
|
||||
|
||||
/* If the SIGTERM thing isn't helping use SIGKILL */
|
||||
kill_some_children(SIGKILL, deleted, spawned);
|
||||
sleep(1);
|
||||
while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
|
||||
const char *dead = "";
|
||||
remove_child(pid);
|
||||
if (!WIFEXITED(status) || (WEXITSTATUS(status) > 0))
|
||||
dead = " (with error)";
|
||||
loginfo("[%"PRIuMAX"] Disconnected%s", (uintmax_t)pid, dead);
|
||||
}
|
||||
}
|
||||
|
||||
static void handle(int incoming, struct sockaddr *addr, int addrlen)
|
||||
{
|
||||
pid_t pid = fork();
|
||||
pid_t pid;
|
||||
|
||||
if (pid) {
|
||||
unsigned idx;
|
||||
|
||||
close(incoming);
|
||||
if (pid < 0)
|
||||
if (max_connections && live_children >= max_connections) {
|
||||
kill_some_child();
|
||||
sleep(1); /* give it some time to die */
|
||||
check_dead_children();
|
||||
if (live_children >= max_connections) {
|
||||
close(incoming);
|
||||
logerror("Too many children, dropping connection");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
idx = children_spawned % MAX_CHILDREN;
|
||||
children_spawned++;
|
||||
add_child(idx, pid, addr, addrlen);
|
||||
if ((pid = fork())) {
|
||||
close(incoming);
|
||||
if (pid < 0) {
|
||||
logerror("Couldn't fork %s", strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
check_max_connections();
|
||||
add_child(pid, addr, addrlen);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -779,21 +697,11 @@ static void handle(int incoming, struct sockaddr *addr, int addrlen)
|
||||
|
||||
static void child_handler(int signo)
|
||||
{
|
||||
for (;;) {
|
||||
int status;
|
||||
pid_t pid = waitpid(-1, &status, WNOHANG);
|
||||
|
||||
if (pid > 0) {
|
||||
unsigned reaped = children_reaped;
|
||||
if (!WIFEXITED(status) || WEXITSTATUS(status) > 0)
|
||||
pid = -pid;
|
||||
dead_child[reaped % MAX_CHILDREN] = pid;
|
||||
children_reaped = reaped + 1;
|
||||
write(child_handler_pipe[1], &status, 1);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
/*
|
||||
* Otherwise empty handler because systemcalls will get interrupted
|
||||
* upon signal receipt
|
||||
* SysV needs the handler to be rearmed
|
||||
*/
|
||||
signal(SIGCHLD, child_handler);
|
||||
}
|
||||
|
||||
@@ -836,7 +744,7 @@ static int socksetup(char *listen_addr, int listen_port, int **socklist_p)
|
||||
if (sockfd < 0)
|
||||
continue;
|
||||
if (sockfd >= FD_SETSIZE) {
|
||||
error("too large socket descriptor.");
|
||||
logerror("Socket descriptor too large");
|
||||
close(sockfd);
|
||||
continue;
|
||||
}
|
||||
@@ -936,35 +844,28 @@ static int service_loop(int socknum, int *socklist)
|
||||
struct pollfd *pfd;
|
||||
int i;
|
||||
|
||||
if (pipe(child_handler_pipe) < 0)
|
||||
die ("Could not set up pipe for child handler");
|
||||
|
||||
pfd = xcalloc(socknum + 1, sizeof(struct pollfd));
|
||||
pfd = xcalloc(socknum, sizeof(struct pollfd));
|
||||
|
||||
for (i = 0; i < socknum; i++) {
|
||||
pfd[i].fd = socklist[i];
|
||||
pfd[i].events = POLLIN;
|
||||
}
|
||||
pfd[socknum].fd = child_handler_pipe[0];
|
||||
pfd[socknum].events = POLLIN;
|
||||
|
||||
signal(SIGCHLD, child_handler);
|
||||
|
||||
for (;;) {
|
||||
int i;
|
||||
|
||||
if (poll(pfd, socknum + 1, -1) < 0) {
|
||||
check_dead_children();
|
||||
|
||||
if (poll(pfd, socknum, -1) < 0) {
|
||||
if (errno != EINTR) {
|
||||
error("poll failed, resuming: %s",
|
||||
logerror("Poll failed, resuming: %s",
|
||||
strerror(errno));
|
||||
sleep(1);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (pfd[socknum].revents & POLLIN) {
|
||||
read(child_handler_pipe[0], &i, 1);
|
||||
check_dead_children();
|
||||
}
|
||||
|
||||
for (i = 0; i < socknum; i++) {
|
||||
if (pfd[i].revents & POLLIN) {
|
||||
@@ -1022,7 +923,7 @@ static void store_pid(const char *path)
|
||||
FILE *f = fopen(path, "w");
|
||||
if (!f)
|
||||
die("cannot open pid file %s: %s", path, strerror(errno));
|
||||
if (fprintf(f, "%d\n", getpid()) < 0 || fclose(f) != 0)
|
||||
if (fprintf(f, "%"PRIuMAX"\n", (uintmax_t) getpid()) < 0 || fclose(f) != 0)
|
||||
die("failed to write pid file %s: %s", path, strerror(errno));
|
||||
}
|
||||
|
||||
@@ -1055,11 +956,6 @@ int main(int argc, char **argv)
|
||||
gid_t gid = 0;
|
||||
int i;
|
||||
|
||||
/* Without this we cannot rely on waitpid() to tell
|
||||
* what happened to our children.
|
||||
*/
|
||||
signal(SIGCHLD, SIG_DFL);
|
||||
|
||||
for (i = 1; i < argc; i++) {
|
||||
char *arg = argv[i];
|
||||
|
||||
@@ -1105,6 +1001,12 @@ int main(int argc, char **argv)
|
||||
init_timeout = atoi(arg+15);
|
||||
continue;
|
||||
}
|
||||
if (!prefixcmp(arg, "--max-connections=")) {
|
||||
max_connections = atoi(arg+18);
|
||||
if (max_connections < 0)
|
||||
max_connections = 0; /* unlimited */
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(arg, "--strict-paths")) {
|
||||
strict_paths = 1;
|
||||
continue;
|
||||
@@ -1178,9 +1080,10 @@ int main(int argc, char **argv)
|
||||
}
|
||||
|
||||
if (log_syslog) {
|
||||
openlog("git-daemon", 0, LOG_DAEMON);
|
||||
openlog("git-daemon", LOG_PID, LOG_DAEMON);
|
||||
set_die_routine(daemon_die);
|
||||
}
|
||||
} else
|
||||
setlinebuf(stderr); /* avoid splitting a message in the middle */
|
||||
|
||||
if (inetd_mode && (group_name || user_name))
|
||||
die("--user and --group are incompatible with --inetd");
|
||||
@@ -1233,8 +1136,10 @@ int main(int argc, char **argv)
|
||||
return execute(peer);
|
||||
}
|
||||
|
||||
if (detach)
|
||||
if (detach) {
|
||||
daemonize();
|
||||
loginfo("Ready to rumble");
|
||||
}
|
||||
else
|
||||
sanitize_stdfds();
|
||||
|
||||
|
||||
17
diff.c
17
diff.c
@@ -1060,6 +1060,13 @@ static long gather_dirstat(FILE *file, struct dirstat_dir *dir, unsigned long ch
|
||||
return this_dir;
|
||||
}
|
||||
|
||||
static int dirstat_compare(const void *_a, const void *_b)
|
||||
{
|
||||
const struct dirstat_file *a = _a;
|
||||
const struct dirstat_file *b = _b;
|
||||
return strcmp(a->name, b->name);
|
||||
}
|
||||
|
||||
static void show_dirstat(struct diff_options *options)
|
||||
{
|
||||
int i;
|
||||
@@ -1071,7 +1078,7 @@ static void show_dirstat(struct diff_options *options)
|
||||
dir.alloc = 0;
|
||||
dir.nr = 0;
|
||||
dir.percent = options->dirstat_percent;
|
||||
dir.cumulative = options->output_format & DIFF_FORMAT_CUMULATIVE;
|
||||
dir.cumulative = DIFF_OPT_TST(options, DIRSTAT_CUMULATIVE);
|
||||
|
||||
changed = 0;
|
||||
for (i = 0; i < q->nr; i++) {
|
||||
@@ -1119,6 +1126,7 @@ static void show_dirstat(struct diff_options *options)
|
||||
return;
|
||||
|
||||
/* Show all directories with more than x% of the changes */
|
||||
qsort(dir.files, dir.nr, sizeof(dir.files[0]), dirstat_compare);
|
||||
gather_dirstat(options->file, &dir, changed, "", 0);
|
||||
}
|
||||
|
||||
@@ -2292,6 +2300,7 @@ void diff_setup(struct diff_options *options)
|
||||
options->break_opt = -1;
|
||||
options->rename_limit = -1;
|
||||
options->dirstat_percent = 3;
|
||||
DIFF_OPT_CLR(options, DIRSTAT_CUMULATIVE);
|
||||
options->context = 3;
|
||||
|
||||
options->change = diff_change;
|
||||
@@ -2464,8 +2473,10 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
|
||||
options->output_format |= DIFF_FORMAT_SHORTSTAT;
|
||||
else if (opt_arg(arg, 'X', "dirstat", &options->dirstat_percent))
|
||||
options->output_format |= DIFF_FORMAT_DIRSTAT;
|
||||
else if (!strcmp(arg, "--cumulative"))
|
||||
options->output_format |= DIFF_FORMAT_CUMULATIVE;
|
||||
else if (!strcmp(arg, "--cumulative")) {
|
||||
options->output_format |= DIFF_FORMAT_DIRSTAT;
|
||||
DIFF_OPT_SET(options, DIRSTAT_CUMULATIVE);
|
||||
}
|
||||
else if (!strcmp(arg, "--check"))
|
||||
options->output_format |= DIFF_FORMAT_CHECKDIFF;
|
||||
else if (!strcmp(arg, "--summary"))
|
||||
|
||||
2
diff.h
2
diff.h
@@ -31,7 +31,6 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q,
|
||||
#define DIFF_FORMAT_PATCH 0x0010
|
||||
#define DIFF_FORMAT_SHORTSTAT 0x0020
|
||||
#define DIFF_FORMAT_DIRSTAT 0x0040
|
||||
#define DIFF_FORMAT_CUMULATIVE 0x0080
|
||||
|
||||
/* These override all above */
|
||||
#define DIFF_FORMAT_NAME 0x0100
|
||||
@@ -64,6 +63,7 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q,
|
||||
#define DIFF_OPT_CHECK_FAILED (1 << 16)
|
||||
#define DIFF_OPT_RELATIVE_NAME (1 << 17)
|
||||
#define DIFF_OPT_IGNORE_SUBMODULES (1 << 18)
|
||||
#define DIFF_OPT_DIRSTAT_CUMULATIVE (1 << 19)
|
||||
#define DIFF_OPT_TST(opts, flag) ((opts)->flags & DIFF_OPT_##flag)
|
||||
#define DIFF_OPT_SET(opts, flag) ((opts)->flags |= DIFF_OPT_##flag)
|
||||
#define DIFF_OPT_CLR(opts, flag) ((opts)->flags &= ~DIFF_OPT_##flag)
|
||||
|
||||
14
dir.c
14
dir.c
@@ -52,11 +52,6 @@ int common_prefix(const char **pathspec)
|
||||
return prefix;
|
||||
}
|
||||
|
||||
static inline int special_char(unsigned char c1)
|
||||
{
|
||||
return !c1 || c1 == '*' || c1 == '[' || c1 == '?' || c1 == '\\';
|
||||
}
|
||||
|
||||
/*
|
||||
* Does 'match' matches the given name?
|
||||
* A match is found if
|
||||
@@ -80,7 +75,7 @@ static int match_one(const char *match, const char *name, int namelen)
|
||||
for (;;) {
|
||||
unsigned char c1 = *match;
|
||||
unsigned char c2 = *name;
|
||||
if (special_char(c1))
|
||||
if (isspecial(c1))
|
||||
break;
|
||||
if (c1 != c2)
|
||||
return 0;
|
||||
@@ -680,17 +675,12 @@ static int cmp_name(const void *p1, const void *p2)
|
||||
*/
|
||||
static int simple_length(const char *match)
|
||||
{
|
||||
const char special[256] = {
|
||||
[0] = 1, ['?'] = 1,
|
||||
['\\'] = 1, ['*'] = 1,
|
||||
['['] = 1
|
||||
};
|
||||
int len = -1;
|
||||
|
||||
for (;;) {
|
||||
unsigned char c = *match++;
|
||||
len++;
|
||||
if (special[c])
|
||||
if (isspecial(c))
|
||||
return len;
|
||||
}
|
||||
}
|
||||
|
||||
20
entry.c
20
entry.c
@@ -111,7 +111,7 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout
|
||||
case S_IFREG:
|
||||
new = read_blob_entry(ce, path, &size);
|
||||
if (!new)
|
||||
return error("git-checkout-index: unable to read sha1 file of %s (%s)",
|
||||
return error("git checkout-index: unable to read sha1 file of %s (%s)",
|
||||
path, sha1_to_hex(ce->sha1));
|
||||
|
||||
/*
|
||||
@@ -132,7 +132,7 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout
|
||||
fd = create_file(path, ce->ce_mode);
|
||||
if (fd < 0) {
|
||||
free(new);
|
||||
return error("git-checkout-index: unable to create file %s (%s)",
|
||||
return error("git checkout-index: unable to create file %s (%s)",
|
||||
path, strerror(errno));
|
||||
}
|
||||
|
||||
@@ -140,12 +140,12 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout
|
||||
close(fd);
|
||||
free(new);
|
||||
if (wrote != size)
|
||||
return error("git-checkout-index: unable to write file %s", path);
|
||||
return error("git checkout-index: unable to write file %s", path);
|
||||
break;
|
||||
case S_IFLNK:
|
||||
new = read_blob_entry(ce, path, &size);
|
||||
if (!new)
|
||||
return error("git-checkout-index: unable to read sha1 file of %s (%s)",
|
||||
return error("git checkout-index: unable to read sha1 file of %s (%s)",
|
||||
path, sha1_to_hex(ce->sha1));
|
||||
if (to_tempfile || !has_symlinks) {
|
||||
if (to_tempfile) {
|
||||
@@ -155,31 +155,31 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout
|
||||
fd = create_file(path, 0666);
|
||||
if (fd < 0) {
|
||||
free(new);
|
||||
return error("git-checkout-index: unable to create "
|
||||
return error("git checkout-index: unable to create "
|
||||
"file %s (%s)", path, strerror(errno));
|
||||
}
|
||||
wrote = write_in_full(fd, new, size);
|
||||
close(fd);
|
||||
free(new);
|
||||
if (wrote != size)
|
||||
return error("git-checkout-index: unable to write file %s",
|
||||
return error("git checkout-index: unable to write file %s",
|
||||
path);
|
||||
} else {
|
||||
wrote = symlink(new, path);
|
||||
free(new);
|
||||
if (wrote)
|
||||
return error("git-checkout-index: unable to create "
|
||||
return error("git checkout-index: unable to create "
|
||||
"symlink %s (%s)", path, strerror(errno));
|
||||
}
|
||||
break;
|
||||
case S_IFGITLINK:
|
||||
if (to_tempfile)
|
||||
return error("git-checkout-index: cannot create temporary subproject %s", path);
|
||||
return error("git checkout-index: cannot create temporary subproject %s", path);
|
||||
if (mkdir(path, 0777) < 0)
|
||||
return error("git-checkout-index: cannot create subproject directory %s", path);
|
||||
return error("git checkout-index: cannot create subproject directory %s", path);
|
||||
break;
|
||||
default:
|
||||
return error("git-checkout-index: unknown file mode for %s", path);
|
||||
return error("git checkout-index: unknown file mode for %s", path);
|
||||
}
|
||||
|
||||
if (state->refresh_cache) {
|
||||
|
||||
@@ -376,7 +376,7 @@ static void dump_marks_helper(FILE *, uintmax_t, struct mark_set *);
|
||||
|
||||
static void write_crash_report(const char *err)
|
||||
{
|
||||
char *loc = git_path("fast_import_crash_%d", getpid());
|
||||
char *loc = git_path("fast_import_crash_%"PRIuMAX, (uintmax_t) getpid());
|
||||
FILE *rpt = fopen(loc, "w");
|
||||
struct branch *b;
|
||||
unsigned long lu;
|
||||
@@ -390,8 +390,8 @@ static void write_crash_report(const char *err)
|
||||
fprintf(stderr, "fast-import: dumping crash report to %s\n", loc);
|
||||
|
||||
fprintf(rpt, "fast-import crash report:\n");
|
||||
fprintf(rpt, " fast-import process: %d\n", getpid());
|
||||
fprintf(rpt, " parent process : %d\n", getppid());
|
||||
fprintf(rpt, " fast-import process: %"PRIuMAX"\n", (uintmax_t) getpid());
|
||||
fprintf(rpt, " parent process : %"PRIuMAX"\n", (uintmax_t) getppid());
|
||||
fprintf(rpt, " at %s\n", show_date(time(NULL), 0, DATE_LOCAL));
|
||||
fputc('\n', rpt);
|
||||
|
||||
@@ -951,7 +951,8 @@ static void end_packfile(void)
|
||||
|
||||
close_pack_windows(pack_data);
|
||||
fixup_pack_header_footer(pack_data->pack_fd, pack_data->sha1,
|
||||
pack_data->pack_name, object_count);
|
||||
pack_data->pack_name, object_count,
|
||||
NULL, 0);
|
||||
close(pack_data->pack_fd);
|
||||
idx_name = keep_pack(create_index());
|
||||
|
||||
|
||||
@@ -329,11 +329,13 @@ extern unsigned char sane_ctype[256];
|
||||
#define GIT_SPACE 0x01
|
||||
#define GIT_DIGIT 0x02
|
||||
#define GIT_ALPHA 0x04
|
||||
#define GIT_SPECIAL 0x08
|
||||
#define sane_istest(x,mask) ((sane_ctype[(unsigned char)(x)] & (mask)) != 0)
|
||||
#define isspace(x) sane_istest(x,GIT_SPACE)
|
||||
#define isdigit(x) sane_istest(x,GIT_DIGIT)
|
||||
#define isalpha(x) sane_istest(x,GIT_ALPHA)
|
||||
#define isalnum(x) sane_istest(x,GIT_ALPHA | GIT_DIGIT)
|
||||
#define isspecial(x) sane_istest(x,GIT_SPECIAL)
|
||||
#define tolower(x) sane_case((unsigned char)(x), 0x20)
|
||||
#define toupper(x) sane_case((unsigned char)(x), 0)
|
||||
|
||||
|
||||
@@ -232,11 +232,11 @@ mkdir ../map || die "Could not create map/ directory"
|
||||
case "$filter_subdir" in
|
||||
"")
|
||||
git rev-list --reverse --topo-order --default HEAD \
|
||||
--parents "$@"
|
||||
--parents --simplify-merges "$@"
|
||||
;;
|
||||
*)
|
||||
git rev-list --reverse --topo-order --default HEAD \
|
||||
--parents "$@" -- "$filter_subdir"
|
||||
--parents --simplify-merges "$@" -- "$filter_subdir"
|
||||
esac > ../revs || die "Could not get the commits"
|
||||
commits=$(wc -l <../revs | tr -d " ")
|
||||
|
||||
@@ -317,24 +317,20 @@ done <../revs
|
||||
|
||||
# In case of a subdirectory filter, it is possible that a specified head
|
||||
# is not in the set of rewritten commits, because it was pruned by the
|
||||
# revision walker. Fix it by mapping these heads to the next rewritten
|
||||
# ancestor(s), i.e. the boundaries in the set of rewritten commits.
|
||||
# revision walker. Fix it by mapping these heads to the unique nearest
|
||||
# ancestor that survived the pruning.
|
||||
|
||||
# NEEDSWORK: we should sort the unmapped refs topologically first
|
||||
while read ref
|
||||
do
|
||||
sha1=$(git rev-parse "$ref"^0)
|
||||
test -f "$workdir"/../map/$sha1 && continue
|
||||
# Assign the boundarie(s) in the set of rewritten commits
|
||||
# as the replacement commit(s).
|
||||
# (This would look a bit nicer if --not --stdin worked.)
|
||||
for p in $( (cd "$workdir"/../map; ls | sed "s/^/^/") |
|
||||
git rev-list $ref --boundary --stdin |
|
||||
sed -n "s/^-//p")
|
||||
if test "$filter_subdir"
|
||||
then
|
||||
while read ref
|
||||
do
|
||||
map $p >> "$workdir"/../map/$sha1
|
||||
done
|
||||
done < "$tempdir"/heads
|
||||
sha1=$(git rev-parse "$ref"^0)
|
||||
test -f "$workdir"/../map/$sha1 && continue
|
||||
ancestor=$(git rev-list --simplify-merges -1 \
|
||||
$ref -- "$filter_subdir")
|
||||
test "$ancestor" && echo $(map $ancestor) >> "$workdir"/../map/$sha1
|
||||
done < "$tempdir"/heads
|
||||
fi
|
||||
|
||||
# Finally update the refs
|
||||
|
||||
|
||||
@@ -657,6 +657,8 @@ proc apply_config {} {
|
||||
}
|
||||
|
||||
set default_config(branch.autosetupmerge) true
|
||||
set default_config(merge.tool) {}
|
||||
set default_config(merge.keepbackup) true
|
||||
set default_config(merge.diffstat) true
|
||||
set default_config(merge.summary) false
|
||||
set default_config(merge.verbosity) 2
|
||||
@@ -668,6 +670,7 @@ set default_config(gui.pruneduringfetch) false
|
||||
set default_config(gui.trustmtime) false
|
||||
set default_config(gui.fastcopyblame) false
|
||||
set default_config(gui.copyblamethreshold) 40
|
||||
set default_config(gui.blamehistoryctx) 7
|
||||
set default_config(gui.diffcontext) 5
|
||||
set default_config(gui.commitmsgwidth) 75
|
||||
set default_config(gui.newbranchtemplate) {}
|
||||
@@ -1323,6 +1326,8 @@ proc rescan_done {fd buf after} {
|
||||
unlock_index
|
||||
display_all_files
|
||||
if {$current_diff_path ne {}} reshow_diff
|
||||
if {$current_diff_path eq {}} select_first_diff
|
||||
|
||||
uplevel #0 $after
|
||||
}
|
||||
|
||||
@@ -1619,6 +1624,15 @@ static unsigned char file_merge_bits[] = {
|
||||
0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
|
||||
} -maskdata $filemask
|
||||
|
||||
image create bitmap file_statechange -background white -foreground green -data {
|
||||
#define file_merge_width 14
|
||||
#define file_merge_height 15
|
||||
static unsigned char file_statechange_bits[] = {
|
||||
0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x62, 0x10,
|
||||
0x62, 0x10, 0xba, 0x11, 0xba, 0x11, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10,
|
||||
0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
|
||||
} -maskdata $filemask
|
||||
|
||||
set ui_index .vpane.files.index.list
|
||||
set ui_workdir .vpane.files.workdir.list
|
||||
|
||||
@@ -1627,12 +1641,14 @@ set all_icons(A$ui_index) file_fulltick
|
||||
set all_icons(M$ui_index) file_fulltick
|
||||
set all_icons(D$ui_index) file_removed
|
||||
set all_icons(U$ui_index) file_merge
|
||||
set all_icons(T$ui_index) file_statechange
|
||||
|
||||
set all_icons(_$ui_workdir) file_plain
|
||||
set all_icons(M$ui_workdir) file_mod
|
||||
set all_icons(D$ui_workdir) file_question
|
||||
set all_icons(U$ui_workdir) file_merge
|
||||
set all_icons(O$ui_workdir) file_plain
|
||||
set all_icons(T$ui_workdir) file_statechange
|
||||
|
||||
set max_status_desc 0
|
||||
foreach i {
|
||||
@@ -1643,6 +1659,9 @@ foreach i {
|
||||
{MM {mc "Portions staged for commit"}}
|
||||
{MD {mc "Staged for commit, missing"}}
|
||||
|
||||
{_T {mc "File type changed, not staged"}}
|
||||
{T_ {mc "File type changed, staged"}}
|
||||
|
||||
{_O {mc "Untracked, not staged"}}
|
||||
{A_ {mc "Staged for commit"}}
|
||||
{AM {mc "Portions staged for commit"}}
|
||||
@@ -1652,10 +1671,12 @@ foreach i {
|
||||
{D_ {mc "Staged for removal"}}
|
||||
{DO {mc "Staged for removal, still present"}}
|
||||
|
||||
{_U {mc "Requires merge resolution"}}
|
||||
{U_ {mc "Requires merge resolution"}}
|
||||
{UU {mc "Requires merge resolution"}}
|
||||
{UM {mc "Requires merge resolution"}}
|
||||
{UD {mc "Requires merge resolution"}}
|
||||
{UT {mc "Requires merge resolution"}}
|
||||
} {
|
||||
set text [eval [lindex $i 1]]
|
||||
if {$max_status_desc < [string length $text]} {
|
||||
@@ -1796,13 +1817,120 @@ proc do_rescan {} {
|
||||
rescan ui_ready
|
||||
}
|
||||
|
||||
proc ui_do_rescan {} {
|
||||
rescan {force_first_diff; ui_ready}
|
||||
}
|
||||
|
||||
proc do_commit {} {
|
||||
commit_tree
|
||||
}
|
||||
|
||||
proc next_diff {} {
|
||||
global next_diff_p next_diff_w next_diff_i
|
||||
show_diff $next_diff_p $next_diff_w $next_diff_i
|
||||
show_diff $next_diff_p $next_diff_w {}
|
||||
}
|
||||
|
||||
proc find_anchor_pos {lst name} {
|
||||
set lid [lsearch -sorted -exact $lst $name]
|
||||
|
||||
if {$lid == -1} {
|
||||
set lid 0
|
||||
foreach lname $lst {
|
||||
if {$lname >= $name} break
|
||||
incr lid
|
||||
}
|
||||
}
|
||||
|
||||
return $lid
|
||||
}
|
||||
|
||||
proc find_file_from {flist idx delta path mmask} {
|
||||
global file_states
|
||||
|
||||
set len [llength $flist]
|
||||
while {$idx >= 0 && $idx < $len} {
|
||||
set name [lindex $flist $idx]
|
||||
|
||||
if {$name ne $path && [info exists file_states($name)]} {
|
||||
set state [lindex $file_states($name) 0]
|
||||
|
||||
if {$mmask eq {} || [regexp $mmask $state]} {
|
||||
return $idx
|
||||
}
|
||||
}
|
||||
|
||||
incr idx $delta
|
||||
}
|
||||
|
||||
return {}
|
||||
}
|
||||
|
||||
proc find_next_diff {w path {lno {}} {mmask {}}} {
|
||||
global next_diff_p next_diff_w next_diff_i
|
||||
global file_lists ui_index ui_workdir
|
||||
|
||||
set flist $file_lists($w)
|
||||
if {$lno eq {}} {
|
||||
set lno [find_anchor_pos $flist $path]
|
||||
} else {
|
||||
incr lno -1
|
||||
}
|
||||
|
||||
if {$mmask ne {} && ![regexp {(^\^)|(\$$)} $mmask]} {
|
||||
if {$w eq $ui_index} {
|
||||
set mmask "^$mmask"
|
||||
} else {
|
||||
set mmask "$mmask\$"
|
||||
}
|
||||
}
|
||||
|
||||
set idx [find_file_from $flist $lno 1 $path $mmask]
|
||||
if {$idx eq {}} {
|
||||
incr lno -1
|
||||
set idx [find_file_from $flist $lno -1 $path $mmask]
|
||||
}
|
||||
|
||||
if {$idx ne {}} {
|
||||
set next_diff_w $w
|
||||
set next_diff_p [lindex $flist $idx]
|
||||
set next_diff_i [expr {$idx+1}]
|
||||
return 1
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
proc next_diff_after_action {w path {lno {}} {mmask {}}} {
|
||||
global current_diff_path
|
||||
|
||||
if {$path ne $current_diff_path} {
|
||||
return {}
|
||||
} elseif {[find_next_diff $w $path $lno $mmask]} {
|
||||
return {next_diff;}
|
||||
} else {
|
||||
return {reshow_diff;}
|
||||
}
|
||||
}
|
||||
|
||||
proc select_first_diff {} {
|
||||
global ui_workdir
|
||||
|
||||
if {[find_next_diff $ui_workdir {} 1 {^_?U}] ||
|
||||
[find_next_diff $ui_workdir {} 1 {[^O]$}]} {
|
||||
next_diff
|
||||
}
|
||||
}
|
||||
|
||||
proc force_first_diff {} {
|
||||
global current_diff_path
|
||||
|
||||
if {[info exists file_states($current_diff_path)]} {
|
||||
set state [lindex $file_states($current_diff_path) 0]
|
||||
|
||||
if {[string index $state 1] ne {O}} return
|
||||
}
|
||||
|
||||
select_first_diff
|
||||
}
|
||||
|
||||
proc toggle_or_diff {w x y} {
|
||||
@@ -1823,34 +1951,27 @@ proc toggle_or_diff {w x y} {
|
||||
$ui_index tag remove in_sel 0.0 end
|
||||
$ui_workdir tag remove in_sel 0.0 end
|
||||
|
||||
# Do not stage files with conflicts
|
||||
if {[info exists file_states($path)]} {
|
||||
set state [lindex $file_states($path) 0]
|
||||
} else {
|
||||
set state {__}
|
||||
}
|
||||
|
||||
if {[string first {U} $state] >= 0} {
|
||||
set col 1
|
||||
}
|
||||
|
||||
# Restage the file, or simply show the diff
|
||||
if {$col == 0 && $y > 1} {
|
||||
set i [expr {$lno-1}]
|
||||
set ll [expr {[llength $file_lists($w)]-1}]
|
||||
|
||||
if {$i == $ll && $i == 0} {
|
||||
set after {reshow_diff;}
|
||||
if {[string index $state 1] eq {O}} {
|
||||
set mmask {}
|
||||
} else {
|
||||
global next_diff_p next_diff_w next_diff_i
|
||||
|
||||
set next_diff_w $w
|
||||
|
||||
if {$i < $ll} {
|
||||
set i [expr {$i + 1}]
|
||||
set next_diff_i $i
|
||||
} else {
|
||||
set next_diff_i $i
|
||||
set i [expr {$i - 1}]
|
||||
}
|
||||
|
||||
set next_diff_p [lindex $file_lists($w) $i]
|
||||
|
||||
if {$next_diff_p ne {} && $current_diff_path ne {}} {
|
||||
set after {next_diff;}
|
||||
} else {
|
||||
set after {}
|
||||
}
|
||||
set mmask {[^O]}
|
||||
}
|
||||
|
||||
set after [next_diff_after_action $w $path $lno $mmask]
|
||||
|
||||
if {$w eq $ui_index} {
|
||||
update_indexinfo \
|
||||
"Unstaging [short_path $path] from commit" \
|
||||
@@ -1932,7 +2053,7 @@ proc show_more_context {} {
|
||||
|
||||
proc show_less_context {} {
|
||||
global repo_config
|
||||
if {$repo_config(gui.diffcontext) >= 1} {
|
||||
if {$repo_config(gui.diffcontext) > 1} {
|
||||
incr repo_config(gui.diffcontext) -1
|
||||
reshow_diff
|
||||
}
|
||||
@@ -2113,7 +2234,7 @@ if {[is_enabled multicommit] || [is_enabled singlecommit]} {
|
||||
.mbar.commit add separator
|
||||
|
||||
.mbar.commit add command -label [mc Rescan] \
|
||||
-command do_rescan \
|
||||
-command ui_do_rescan \
|
||||
-accelerator F5
|
||||
lappend disable_on_lock \
|
||||
[list .mbar.commit entryconf [.mbar.commit index last] -state]
|
||||
@@ -2281,10 +2402,15 @@ proc usage {} {
|
||||
switch -- $subcommand {
|
||||
browser -
|
||||
blame {
|
||||
set subcommand_args {rev? path}
|
||||
if {$subcommand eq "blame"} {
|
||||
set subcommand_args {[--line=<num>] rev? path}
|
||||
} else {
|
||||
set subcommand_args {rev? path}
|
||||
}
|
||||
if {$argv eq {}} usage
|
||||
set head {}
|
||||
set path {}
|
||||
set jump_spec {}
|
||||
set is_path 0
|
||||
foreach a $argv {
|
||||
if {$is_path || [file exists $_prefix$a]} {
|
||||
@@ -2298,6 +2424,9 @@ blame {
|
||||
set path {}
|
||||
}
|
||||
set is_path 1
|
||||
} elseif {[regexp {^--line=(\d+)$} $a a lnum]} {
|
||||
if {$jump_spec ne {} || $head ne {}} usage
|
||||
set jump_spec [list $lnum]
|
||||
} elseif {$head eq {}} {
|
||||
if {$head ne {}} usage
|
||||
set head $a
|
||||
@@ -2329,6 +2458,7 @@ blame {
|
||||
|
||||
switch -- $subcommand {
|
||||
browser {
|
||||
if {$jump_spec ne {}} usage
|
||||
if {$head eq {}} {
|
||||
if {$path ne {} && [file isdirectory $path]} {
|
||||
set head $current_branch
|
||||
@@ -2344,7 +2474,7 @@ blame {
|
||||
puts stderr [mc "fatal: cannot stat path %s: No such file or directory" $path]
|
||||
exit 1
|
||||
}
|
||||
blame::new $head $path
|
||||
blame::new $head $path $jump_spec
|
||||
}
|
||||
}
|
||||
return
|
||||
@@ -2460,7 +2590,7 @@ pack .vpane.lower.commarea.buttons.l -side top -fill x
|
||||
pack .vpane.lower.commarea.buttons -side left -fill y
|
||||
|
||||
button .vpane.lower.commarea.buttons.rescan -text [mc Rescan] \
|
||||
-command do_rescan
|
||||
-command ui_do_rescan
|
||||
pack .vpane.lower.commarea.buttons.rescan -side top -fill x
|
||||
lappend disable_on_lock \
|
||||
{.vpane.lower.commarea.buttons.rescan conf -state}
|
||||
@@ -2689,6 +2819,51 @@ $ui_diff tag raise sel
|
||||
|
||||
# -- Diff Body Context Menu
|
||||
#
|
||||
|
||||
proc create_common_diff_popup {ctxm} {
|
||||
$ctxm add command \
|
||||
-label [mc "Show Less Context"] \
|
||||
-command show_less_context
|
||||
lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
|
||||
$ctxm add command \
|
||||
-label [mc "Show More Context"] \
|
||||
-command show_more_context
|
||||
lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
|
||||
$ctxm add separator
|
||||
$ctxm add command \
|
||||
-label [mc Refresh] \
|
||||
-command reshow_diff
|
||||
lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
|
||||
$ctxm add command \
|
||||
-label [mc Copy] \
|
||||
-command {tk_textCopy $ui_diff}
|
||||
lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
|
||||
$ctxm add command \
|
||||
-label [mc "Select All"] \
|
||||
-command {focus $ui_diff;$ui_diff tag add sel 0.0 end}
|
||||
lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
|
||||
$ctxm add command \
|
||||
-label [mc "Copy All"] \
|
||||
-command {
|
||||
$ui_diff tag add sel 0.0 end
|
||||
tk_textCopy $ui_diff
|
||||
$ui_diff tag remove sel 0.0 end
|
||||
}
|
||||
lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
|
||||
$ctxm add separator
|
||||
$ctxm add command \
|
||||
-label [mc "Decrease Font Size"] \
|
||||
-command {incr_font_size font_diff -1}
|
||||
lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
|
||||
$ctxm add command \
|
||||
-label [mc "Increase Font Size"] \
|
||||
-command {incr_font_size font_diff 1}
|
||||
lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
|
||||
$ctxm add separator
|
||||
$ctxm add command -label [mc "Options..."] \
|
||||
-command do_options
|
||||
}
|
||||
|
||||
set ctxm .vpane.lower.diff.body.ctxm
|
||||
menu $ctxm -tearoff 0
|
||||
$ctxm add command \
|
||||
@@ -2702,71 +2877,65 @@ $ctxm add command \
|
||||
set ui_diff_applyline [$ctxm index last]
|
||||
lappend diff_actions [list $ctxm entryconf $ui_diff_applyline -state]
|
||||
$ctxm add separator
|
||||
$ctxm add command \
|
||||
-label [mc "Show Less Context"] \
|
||||
-command show_less_context
|
||||
lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
|
||||
$ctxm add command \
|
||||
-label [mc "Show More Context"] \
|
||||
-command show_more_context
|
||||
lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
|
||||
$ctxm add separator
|
||||
$ctxm add command \
|
||||
-label [mc Refresh] \
|
||||
-command reshow_diff
|
||||
lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
|
||||
$ctxm add command \
|
||||
-label [mc Copy] \
|
||||
-command {tk_textCopy $ui_diff}
|
||||
lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
|
||||
$ctxm add command \
|
||||
-label [mc "Select All"] \
|
||||
-command {focus $ui_diff;$ui_diff tag add sel 0.0 end}
|
||||
lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
|
||||
$ctxm add command \
|
||||
-label [mc "Copy All"] \
|
||||
-command {
|
||||
$ui_diff tag add sel 0.0 end
|
||||
tk_textCopy $ui_diff
|
||||
$ui_diff tag remove sel 0.0 end
|
||||
}
|
||||
lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
|
||||
$ctxm add separator
|
||||
$ctxm add command \
|
||||
-label [mc "Decrease Font Size"] \
|
||||
-command {incr_font_size font_diff -1}
|
||||
lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
|
||||
$ctxm add command \
|
||||
-label [mc "Increase Font Size"] \
|
||||
-command {incr_font_size font_diff 1}
|
||||
lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
|
||||
$ctxm add separator
|
||||
$ctxm add command -label [mc "Options..."] \
|
||||
-command do_options
|
||||
proc popup_diff_menu {ctxm x y X Y} {
|
||||
create_common_diff_popup $ctxm
|
||||
|
||||
set ctxmmg .vpane.lower.diff.body.ctxmmg
|
||||
menu $ctxmmg -tearoff 0
|
||||
$ctxmmg add command \
|
||||
-label [mc "Run Merge Tool"] \
|
||||
-command {merge_resolve_tool}
|
||||
lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
|
||||
$ctxmmg add separator
|
||||
$ctxmmg add command \
|
||||
-label [mc "Use Remote Version"] \
|
||||
-command {merge_resolve_one 3}
|
||||
lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
|
||||
$ctxmmg add command \
|
||||
-label [mc "Use Local Version"] \
|
||||
-command {merge_resolve_one 2}
|
||||
lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
|
||||
$ctxmmg add command \
|
||||
-label [mc "Revert To Base"] \
|
||||
-command {merge_resolve_one 1}
|
||||
lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state]
|
||||
$ctxmmg add separator
|
||||
create_common_diff_popup $ctxmmg
|
||||
|
||||
proc popup_diff_menu {ctxm ctxmmg x y X Y} {
|
||||
global current_diff_path file_states
|
||||
set ::cursorX $x
|
||||
set ::cursorY $y
|
||||
if {$::ui_index eq $::current_diff_side} {
|
||||
set l [mc "Unstage Hunk From Commit"]
|
||||
set t [mc "Unstage Line From Commit"]
|
||||
if {[info exists file_states($current_diff_path)]} {
|
||||
set state [lindex $file_states($current_diff_path) 0]
|
||||
} else {
|
||||
set l [mc "Stage Hunk For Commit"]
|
||||
set t [mc "Stage Line For Commit"]
|
||||
set state {__}
|
||||
}
|
||||
if {$::is_3way_diff
|
||||
|| $current_diff_path eq {}
|
||||
|| ![info exists file_states($current_diff_path)]
|
||||
|| {_O} eq [lindex $file_states($current_diff_path) 0]} {
|
||||
set s disabled
|
||||
if {[string first {U} $state] >= 0} {
|
||||
tk_popup $ctxmmg $X $Y
|
||||
} else {
|
||||
set s normal
|
||||
if {$::ui_index eq $::current_diff_side} {
|
||||
set l [mc "Unstage Hunk From Commit"]
|
||||
set t [mc "Unstage Line From Commit"]
|
||||
} else {
|
||||
set l [mc "Stage Hunk For Commit"]
|
||||
set t [mc "Stage Line For Commit"]
|
||||
}
|
||||
if {$::is_3way_diff
|
||||
|| $current_diff_path eq {}
|
||||
|| {__} eq $state
|
||||
|| {_O} eq $state
|
||||
|| {_T} eq $state
|
||||
|| {T_} eq $state} {
|
||||
set s disabled
|
||||
} else {
|
||||
set s normal
|
||||
}
|
||||
$ctxm entryconf $::ui_diff_applyhunk -state $s -label $l
|
||||
$ctxm entryconf $::ui_diff_applyline -state $s -label $t
|
||||
tk_popup $ctxm $X $Y
|
||||
}
|
||||
$ctxm entryconf $::ui_diff_applyhunk -state $s -label $l
|
||||
$ctxm entryconf $::ui_diff_applyline -state $s -label $t
|
||||
tk_popup $ctxm $X $Y
|
||||
}
|
||||
bind_button3 $ui_diff [list popup_diff_menu $ctxm %x %y %X %Y]
|
||||
bind_button3 $ui_diff [list popup_diff_menu $ctxm $ctxmmg %x %y %X %Y]
|
||||
|
||||
# -- Status Bar
|
||||
#
|
||||
@@ -2842,9 +3011,9 @@ if {[is_enabled transport]} {
|
||||
bind . <$M1B-Key-P> do_push_anywhere
|
||||
}
|
||||
|
||||
bind . <Key-F5> do_rescan
|
||||
bind . <$M1B-Key-r> do_rescan
|
||||
bind . <$M1B-Key-R> do_rescan
|
||||
bind . <Key-F5> ui_do_rescan
|
||||
bind . <$M1B-Key-r> ui_do_rescan
|
||||
bind . <$M1B-Key-R> ui_do_rescan
|
||||
bind . <$M1B-Key-s> do_signoff
|
||||
bind . <$M1B-Key-S> do_signoff
|
||||
bind . <$M1B-Key-t> do_add_selection
|
||||
|
||||
@@ -58,7 +58,7 @@ field tooltip_t {} ; # Text widget in $tooltip_wm
|
||||
field tooltip_timer {} ; # Current timer event for our tooltip
|
||||
field tooltip_commit {} ; # Commit(s) in tooltip
|
||||
|
||||
constructor new {i_commit i_path} {
|
||||
constructor new {i_commit i_path i_jump} {
|
||||
global cursor_ptr
|
||||
variable active_color
|
||||
variable group_colors
|
||||
@@ -259,6 +259,12 @@ constructor new {i_commit i_path} {
|
||||
$w.ctxm add command \
|
||||
-label [mc "Do Full Copy Detection"] \
|
||||
-command [cb _fullcopyblame]
|
||||
$w.ctxm add command \
|
||||
-label [mc "Show History Context"] \
|
||||
-command [cb _gitkcommit]
|
||||
$w.ctxm add command \
|
||||
-label [mc "Blame Parent Commit"] \
|
||||
-command [cb _blameparent]
|
||||
|
||||
foreach i $w_columns {
|
||||
for {set g 0} {$g < [llength $group_colors]} {incr g} {
|
||||
@@ -332,7 +338,7 @@ constructor new {i_commit i_path} {
|
||||
wm protocol $top WM_DELETE_WINDOW "destroy $top"
|
||||
bind $top <Destroy> [cb _kill]
|
||||
|
||||
_load $this {}
|
||||
_load $this $i_jump
|
||||
}
|
||||
|
||||
method _kill {} {
|
||||
@@ -787,19 +793,27 @@ method _load_commit {cur_w cur_d pos} {
|
||||
set lno [lindex [split [$cur_w index $pos] .] 0]
|
||||
set dat [lindex $line_data $lno]
|
||||
if {$dat ne {}} {
|
||||
lappend history [list \
|
||||
$commit $path \
|
||||
$highlight_column \
|
||||
$highlight_line \
|
||||
[lindex [$w_file xview] 0] \
|
||||
[lindex [$w_file yview] 0] \
|
||||
]
|
||||
set commit [lindex $dat 0]
|
||||
set path [lindex $dat 1]
|
||||
_load $this [list [lindex $dat 2]]
|
||||
_load_new_commit $this \
|
||||
[lindex $dat 0] \
|
||||
[lindex $dat 1] \
|
||||
[list [lindex $dat 2]]
|
||||
}
|
||||
}
|
||||
|
||||
method _load_new_commit {new_commit new_path jump} {
|
||||
lappend history [list \
|
||||
$commit $path \
|
||||
$highlight_column \
|
||||
$highlight_line \
|
||||
[lindex [$w_file xview] 0] \
|
||||
[lindex [$w_file yview] 0] \
|
||||
]
|
||||
|
||||
set commit $new_commit
|
||||
set path $new_path
|
||||
_load $this $jump
|
||||
}
|
||||
|
||||
method _showcommit {cur_w lno} {
|
||||
global repo_config
|
||||
variable active_color
|
||||
@@ -905,10 +919,14 @@ method _showcommit {cur_w lno} {
|
||||
}
|
||||
}
|
||||
|
||||
method _copycommit {} {
|
||||
method _get_click_amov_info {} {
|
||||
set pos @$::cursorX,$::cursorY
|
||||
set lno [lindex [split [$::cursorW index $pos] .] 0]
|
||||
set dat [lindex $amov_data $lno]
|
||||
return [lindex $amov_data $lno]
|
||||
}
|
||||
|
||||
method _copycommit {} {
|
||||
set dat [_get_click_amov_info $this]
|
||||
if {$dat ne {}} {
|
||||
clipboard clear
|
||||
clipboard append \
|
||||
@@ -918,6 +936,124 @@ method _copycommit {} {
|
||||
}
|
||||
}
|
||||
|
||||
method _format_offset_date {base offset} {
|
||||
set exval [expr {$base + $offset*24*60*60}]
|
||||
return [clock format $exval -format {%Y-%m-%d}]
|
||||
}
|
||||
|
||||
method _gitkcommit {} {
|
||||
set dat [_get_click_amov_info $this]
|
||||
if {$dat ne {}} {
|
||||
set cmit [lindex $dat 0]
|
||||
set radius [get_config gui.blamehistoryctx]
|
||||
set cmdline [list --select-commit=$cmit]
|
||||
|
||||
if {$radius > 0} {
|
||||
set author_time {}
|
||||
set committer_time {}
|
||||
|
||||
catch {set author_time $header($cmit,author-time)}
|
||||
catch {set committer_time $header($cmit,committer-time)}
|
||||
|
||||
if {$committer_time eq {}} {
|
||||
set committer_time $author_time
|
||||
}
|
||||
|
||||
set after_time [_format_offset_date $this $committer_time [expr {-$radius}]]
|
||||
set before_time [_format_offset_date $this $committer_time $radius]
|
||||
|
||||
lappend cmdline --after=$after_time --before=$before_time
|
||||
}
|
||||
|
||||
lappend cmdline $cmit
|
||||
|
||||
set base_rev "HEAD"
|
||||
if {$commit ne {}} {
|
||||
set base_rev $commit
|
||||
}
|
||||
|
||||
if {$base_rev ne $cmit} {
|
||||
lappend cmdline $base_rev
|
||||
}
|
||||
|
||||
do_gitk $cmdline
|
||||
}
|
||||
}
|
||||
|
||||
method _blameparent {} {
|
||||
set dat [_get_click_amov_info $this]
|
||||
if {$dat ne {}} {
|
||||
set cmit [lindex $dat 0]
|
||||
set new_path [lindex $dat 1]
|
||||
|
||||
if {[catch {set cparent [git rev-parse --verify "$cmit^"]}]} {
|
||||
error_popup [strcat [mc "Cannot find parent commit:"] "\n\n$err"]
|
||||
return;
|
||||
}
|
||||
|
||||
_kill $this
|
||||
|
||||
# Generate a diff between the commit and its parent,
|
||||
# and use the hunks to update the line number.
|
||||
# Request zero context to simplify calculations.
|
||||
if {[catch {set fd [eval git_read diff-tree \
|
||||
--unified=0 $cparent $cmit $new_path]} err]} {
|
||||
$status stop [mc "Unable to display parent"]
|
||||
error_popup [strcat [mc "Error loading diff:"] "\n\n$err"]
|
||||
return
|
||||
}
|
||||
|
||||
set r_orig_line [lindex $dat 2]
|
||||
|
||||
fconfigure $fd \
|
||||
-blocking 0 \
|
||||
-encoding binary \
|
||||
-translation binary
|
||||
fileevent $fd readable [cb _read_diff_load_commit \
|
||||
$fd $cparent $new_path $r_orig_line]
|
||||
set current_fd $fd
|
||||
}
|
||||
}
|
||||
|
||||
method _read_diff_load_commit {fd cparent new_path tline} {
|
||||
if {$fd ne $current_fd} {
|
||||
catch {close $fd}
|
||||
return
|
||||
}
|
||||
|
||||
while {[gets $fd line] >= 0} {
|
||||
if {[regexp {^@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@} $line line \
|
||||
old_line osz old_size new_line nsz new_size]} {
|
||||
|
||||
if {$osz eq {}} { set old_size 1 }
|
||||
if {$nsz eq {}} { set new_size 1 }
|
||||
|
||||
if {$new_line <= $tline} {
|
||||
if {[expr {$new_line + $new_size}] > $tline} {
|
||||
# Target line within the hunk
|
||||
set line_shift [expr {
|
||||
($new_size-$old_size)*($tline-$new_line)/$new_size
|
||||
}]
|
||||
} else {
|
||||
set line_shift [expr {$new_size-$old_size}]
|
||||
}
|
||||
|
||||
set r_orig_line [expr {$r_orig_line - $line_shift}]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if {[eof $fd]} {
|
||||
close $fd;
|
||||
set current_fd {}
|
||||
|
||||
_load_new_commit $this \
|
||||
$cparent \
|
||||
$new_path \
|
||||
[list $r_orig_line]
|
||||
}
|
||||
} ifdeleted { catch {close $fd} }
|
||||
|
||||
method _show_tooltip {cur_w pos} {
|
||||
if {$tooltip_wm ne {}} {
|
||||
_open_tooltip $this $cur_w
|
||||
|
||||
@@ -151,7 +151,7 @@ method _enter {} {
|
||||
append p [lindex $n 1]
|
||||
}
|
||||
append p $name
|
||||
blame::new $browser_commit $p
|
||||
blame::new $browser_commit $p {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,7 +149,9 @@ The rescan will be automatically started now.
|
||||
_? {continue}
|
||||
A? -
|
||||
D? -
|
||||
T_ -
|
||||
M? {set files_ready 1}
|
||||
_U -
|
||||
U? {
|
||||
error_popup [mc "Unmerged files cannot be committed.
|
||||
|
||||
@@ -428,6 +430,7 @@ A rescan will be automatically started now.
|
||||
__ -
|
||||
A_ -
|
||||
M_ -
|
||||
T_ -
|
||||
D_ {
|
||||
unset file_states($path)
|
||||
catch {unset selected_paths($path)}
|
||||
|
||||
@@ -24,10 +24,16 @@ proc reshow_diff {} {
|
||||
set p $current_diff_path
|
||||
if {$p eq {}} {
|
||||
# No diff is being shown.
|
||||
} elseif {$current_diff_side eq {}
|
||||
|| [catch {set s $file_states($p)}]
|
||||
|| [lsearch -sorted -exact $file_lists($current_diff_side) $p] == -1} {
|
||||
} elseif {$current_diff_side eq {}} {
|
||||
clear_diff
|
||||
} elseif {[catch {set s $file_states($p)}]
|
||||
|| [lsearch -sorted -exact $file_lists($current_diff_side) $p] == -1} {
|
||||
|
||||
if {[find_next_diff $current_diff_side $p {} {[^O]}]} {
|
||||
next_diff
|
||||
} else {
|
||||
clear_diff
|
||||
}
|
||||
} else {
|
||||
set save_pos [lindex [$ui_diff yview] 0]
|
||||
show_diff $p $current_diff_side {} $save_pos
|
||||
@@ -59,6 +65,7 @@ proc show_diff {path w {lno {}} {scroll_pos {}}} {
|
||||
global is_3way_diff diff_active repo_config
|
||||
global ui_diff ui_index ui_workdir
|
||||
global current_diff_path current_diff_side current_diff_header
|
||||
global current_diff_queue
|
||||
|
||||
if {$diff_active || ![lock_index read]} return
|
||||
|
||||
@@ -71,17 +78,74 @@ proc show_diff {path w {lno {}} {scroll_pos {}}} {
|
||||
}
|
||||
if {$lno >= 1} {
|
||||
$w tag add in_diff $lno.0 [expr {$lno + 1}].0
|
||||
$w see $lno.0
|
||||
}
|
||||
|
||||
set s $file_states($path)
|
||||
set m [lindex $s 0]
|
||||
set is_3way_diff 0
|
||||
set diff_active 1
|
||||
set current_diff_path $path
|
||||
set current_diff_side $w
|
||||
set current_diff_header {}
|
||||
set current_diff_queue {}
|
||||
ui_status [mc "Loading diff of %s..." [escape_path $path]]
|
||||
|
||||
if {[string first {U} $m] >= 0} {
|
||||
merge_load_stages $path [list show_unmerged_diff $scroll_pos]
|
||||
} elseif {$m eq {_O}} {
|
||||
show_other_diff $path $w $m $scroll_pos
|
||||
} else {
|
||||
start_show_diff $scroll_pos
|
||||
}
|
||||
}
|
||||
|
||||
proc show_unmerged_diff {scroll_pos} {
|
||||
global current_diff_path current_diff_side
|
||||
global merge_stages ui_diff
|
||||
global current_diff_queue
|
||||
|
||||
if {$merge_stages(2) eq {}} {
|
||||
lappend current_diff_queue \
|
||||
[list "LOCAL: deleted\nREMOTE:\n" d======= \
|
||||
[list ":1:$current_diff_path" ":3:$current_diff_path"]]
|
||||
} elseif {$merge_stages(3) eq {}} {
|
||||
lappend current_diff_queue \
|
||||
[list "REMOTE: deleted\nLOCAL:\n" d======= \
|
||||
[list ":1:$current_diff_path" ":2:$current_diff_path"]]
|
||||
} elseif {[lindex $merge_stages(1) 0] eq {120000}
|
||||
|| [lindex $merge_stages(2) 0] eq {120000}
|
||||
|| [lindex $merge_stages(3) 0] eq {120000}} {
|
||||
lappend current_diff_queue \
|
||||
[list "LOCAL:\n" d======= \
|
||||
[list ":1:$current_diff_path" ":2:$current_diff_path"]]
|
||||
lappend current_diff_queue \
|
||||
[list "REMOTE:\n" d======= \
|
||||
[list ":1:$current_diff_path" ":3:$current_diff_path"]]
|
||||
} else {
|
||||
start_show_diff $scroll_pos
|
||||
return
|
||||
}
|
||||
|
||||
advance_diff_queue $scroll_pos
|
||||
}
|
||||
|
||||
proc advance_diff_queue {scroll_pos} {
|
||||
global current_diff_queue ui_diff
|
||||
|
||||
set item [lindex $current_diff_queue 0]
|
||||
set current_diff_queue [lrange $current_diff_queue 1 end]
|
||||
|
||||
$ui_diff conf -state normal
|
||||
$ui_diff insert end [lindex $item 0] [lindex $item 1]
|
||||
$ui_diff conf -state disabled
|
||||
|
||||
start_show_diff $scroll_pos [lindex $item 2]
|
||||
}
|
||||
|
||||
proc show_other_diff {path w m scroll_pos} {
|
||||
global file_states file_lists
|
||||
global is_3way_diff diff_active repo_config
|
||||
global ui_diff ui_index ui_workdir
|
||||
global current_diff_path current_diff_side current_diff_header
|
||||
|
||||
# - Git won't give us the diff, there's nothing to compare to!
|
||||
#
|
||||
if {$m eq {_O}} {
|
||||
@@ -160,13 +224,29 @@ proc show_diff {path w {lno {}} {scroll_pos {}}} {
|
||||
ui_ready
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
proc start_show_diff {scroll_pos {add_opts {}}} {
|
||||
global file_states file_lists
|
||||
global is_3way_diff diff_active repo_config
|
||||
global ui_diff ui_index ui_workdir
|
||||
global current_diff_path current_diff_side current_diff_header
|
||||
|
||||
set path $current_diff_path
|
||||
set w $current_diff_side
|
||||
|
||||
set s $file_states($path)
|
||||
set m [lindex $s 0]
|
||||
set is_3way_diff 0
|
||||
set diff_active 1
|
||||
set current_diff_header {}
|
||||
|
||||
set cmd [list]
|
||||
if {$w eq $ui_index} {
|
||||
lappend cmd diff-index
|
||||
lappend cmd --cached
|
||||
} elseif {$w eq $ui_workdir} {
|
||||
if {[string index $m 0] eq {U}} {
|
||||
if {[string first {U} $m] >= 0} {
|
||||
lappend cmd diff
|
||||
} else {
|
||||
lappend cmd diff-files
|
||||
@@ -175,14 +255,18 @@ proc show_diff {path w {lno {}} {scroll_pos {}}} {
|
||||
|
||||
lappend cmd -p
|
||||
lappend cmd --no-color
|
||||
if {$repo_config(gui.diffcontext) >= 0} {
|
||||
if {$repo_config(gui.diffcontext) >= 1} {
|
||||
lappend cmd "-U$repo_config(gui.diffcontext)"
|
||||
}
|
||||
if {$w eq $ui_index} {
|
||||
lappend cmd [PARENT]
|
||||
}
|
||||
lappend cmd --
|
||||
lappend cmd $path
|
||||
if {$add_opts ne {}} {
|
||||
eval lappend cmd $add_opts
|
||||
} else {
|
||||
lappend cmd --
|
||||
lappend cmd $path
|
||||
}
|
||||
|
||||
if {[catch {set fd [eval git_read --nice $cmd]} err]} {
|
||||
set diff_active 0
|
||||
@@ -192,6 +276,7 @@ proc show_diff {path w {lno {}} {scroll_pos {}}} {
|
||||
return
|
||||
}
|
||||
|
||||
set ::current_diff_inheader 1
|
||||
fconfigure $fd \
|
||||
-blocking 0 \
|
||||
-encoding binary \
|
||||
@@ -202,23 +287,27 @@ proc show_diff {path w {lno {}} {scroll_pos {}}} {
|
||||
proc read_diff {fd scroll_pos} {
|
||||
global ui_diff diff_active
|
||||
global is_3way_diff current_diff_header
|
||||
global current_diff_queue
|
||||
|
||||
$ui_diff conf -state normal
|
||||
while {[gets $fd line] >= 0} {
|
||||
# -- Cleanup uninteresting diff header lines.
|
||||
#
|
||||
if { [string match {diff --git *} $line]
|
||||
|| [string match {diff --cc *} $line]
|
||||
|| [string match {diff --combined *} $line]
|
||||
|| [string match {--- *} $line]
|
||||
|| [string match {+++ *} $line]} {
|
||||
append current_diff_header $line "\n"
|
||||
continue
|
||||
if {$::current_diff_inheader} {
|
||||
if { [string match {diff --git *} $line]
|
||||
|| [string match {diff --cc *} $line]
|
||||
|| [string match {diff --combined *} $line]
|
||||
|| [string match {--- *} $line]
|
||||
|| [string match {+++ *} $line]} {
|
||||
append current_diff_header $line "\n"
|
||||
continue
|
||||
}
|
||||
}
|
||||
if {[string match {index *} $line]} continue
|
||||
if {$line eq {deleted file mode 120000}} {
|
||||
set line "deleted symlink"
|
||||
}
|
||||
set ::current_diff_inheader 0
|
||||
|
||||
# -- Automatically detect if this is a 3 way diff.
|
||||
#
|
||||
@@ -286,6 +375,12 @@ proc read_diff {fd scroll_pos} {
|
||||
|
||||
if {[eof $fd]} {
|
||||
close $fd
|
||||
|
||||
if {$current_diff_queue ne {}} {
|
||||
advance_diff_queue $scroll_pos
|
||||
return
|
||||
}
|
||||
|
||||
set diff_active 0
|
||||
unlock_index
|
||||
if {$scroll_pos ne {}} {
|
||||
@@ -366,10 +461,9 @@ proc apply_hunk {x y} {
|
||||
}
|
||||
unlock_index
|
||||
display_file $current_diff_path $mi
|
||||
# This should trigger shift to the next changed file
|
||||
if {$o eq {_}} {
|
||||
clear_diff
|
||||
} else {
|
||||
set current_diff_path $current_diff_path
|
||||
reshow_diff
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -99,6 +99,7 @@ proc write_update_indexinfo {fd pathList totalCnt batch after} {
|
||||
switch -glob -- [lindex $s 0] {
|
||||
A? {set new _O}
|
||||
M? {set new _M}
|
||||
T_ {set new _T}
|
||||
D_ {set new _D}
|
||||
D? {set new _?}
|
||||
?? {continue}
|
||||
@@ -162,6 +163,8 @@ proc write_update_index {fd pathList totalCnt batch after} {
|
||||
?D {set new D_}
|
||||
_O -
|
||||
AM {set new A_}
|
||||
_T {set new T_}
|
||||
_U -
|
||||
U? {
|
||||
if {[file exists $path]} {
|
||||
set new M_
|
||||
@@ -231,6 +234,7 @@ proc write_checkout_index {fd pathList totalCnt batch after} {
|
||||
switch -glob -- [lindex $file_states($path) 0] {
|
||||
U? {continue}
|
||||
?M -
|
||||
?T -
|
||||
?D {
|
||||
puts -nonewline $fd "[encoding convertto $path]\0"
|
||||
display_file $path ?_
|
||||
@@ -252,6 +256,7 @@ proc unstage_helper {txt paths} {
|
||||
switch -glob -- [lindex $file_states($path) 0] {
|
||||
A? -
|
||||
M? -
|
||||
T_ -
|
||||
D? {
|
||||
lappend pathList $path
|
||||
if {$path eq $current_diff_path} {
|
||||
@@ -296,6 +301,7 @@ proc add_helper {txt paths} {
|
||||
_O -
|
||||
?M -
|
||||
?D -
|
||||
?T -
|
||||
U? {
|
||||
lappend pathList $path
|
||||
if {$path eq $current_diff_path} {
|
||||
@@ -336,6 +342,7 @@ proc do_add_all {} {
|
||||
switch -glob -- [lindex $file_states($path) 0] {
|
||||
U? {continue}
|
||||
?M -
|
||||
?T -
|
||||
?D {lappend paths $path}
|
||||
}
|
||||
}
|
||||
@@ -353,6 +360,7 @@ proc revert_helper {txt paths} {
|
||||
switch -glob -- [lindex $file_states($path) 0] {
|
||||
U? {continue}
|
||||
?M -
|
||||
?T -
|
||||
?D {
|
||||
lappend pathList $path
|
||||
if {$path eq $current_diff_path} {
|
||||
@@ -409,11 +417,11 @@ proc do_revert_selection {} {
|
||||
|
||||
if {[array size selected_paths] > 0} {
|
||||
revert_helper \
|
||||
{Reverting selected files} \
|
||||
[mc "Reverting selected files"] \
|
||||
[array names selected_paths]
|
||||
} elseif {$current_diff_path ne {}} {
|
||||
revert_helper \
|
||||
"Reverting [short_path $current_diff_path]" \
|
||||
[mc "Reverting %s" [short_path $current_diff_path]] \
|
||||
[list $current_diff_path]
|
||||
}
|
||||
}
|
||||
|
||||
373
git-gui/lib/mergetool.tcl
Normal file
373
git-gui/lib/mergetool.tcl
Normal file
@@ -0,0 +1,373 @@
|
||||
# git-gui merge conflict resolution
|
||||
# parts based on git-mergetool (c) 2006 Theodore Y. Ts'o
|
||||
|
||||
proc merge_resolve_one {stage} {
|
||||
global current_diff_path
|
||||
|
||||
switch -- $stage {
|
||||
1 { set target [mc "the base version"] }
|
||||
2 { set target [mc "this branch"] }
|
||||
3 { set target [mc "the other branch"] }
|
||||
}
|
||||
|
||||
set op_question [mc "Force resolution to %s?
|
||||
Note that the diff shows only conflicting changes.
|
||||
|
||||
%s will be overwritten.
|
||||
|
||||
This operation can be undone only by restarting the merge." \
|
||||
$target [short_path $current_diff_path]]
|
||||
|
||||
if {[ask_popup $op_question] eq {yes}} {
|
||||
merge_load_stages $current_diff_path [list merge_force_stage $stage]
|
||||
}
|
||||
}
|
||||
|
||||
proc merge_add_resolution {path} {
|
||||
global current_diff_path ui_workdir
|
||||
|
||||
set after [next_diff_after_action $ui_workdir $path {} {^_?U}]
|
||||
|
||||
update_index \
|
||||
[mc "Adding resolution for %s" [short_path $path]] \
|
||||
[list $path] \
|
||||
[concat $after [list ui_ready]]
|
||||
}
|
||||
|
||||
proc merge_force_stage {stage} {
|
||||
global current_diff_path merge_stages
|
||||
|
||||
if {$merge_stages($stage) ne {}} {
|
||||
git checkout-index -f --stage=$stage -- $current_diff_path
|
||||
} else {
|
||||
file delete -- $current_diff_path
|
||||
}
|
||||
|
||||
merge_add_resolution $current_diff_path
|
||||
}
|
||||
|
||||
proc merge_load_stages {path cont} {
|
||||
global merge_stages_fd merge_stages merge_stages_buf
|
||||
|
||||
if {[info exists merge_stages_fd]} {
|
||||
catch { kill_file_process $merge_stages_fd }
|
||||
catch { close $merge_stages_fd }
|
||||
}
|
||||
|
||||
set merge_stages(0) {}
|
||||
set merge_stages(1) {}
|
||||
set merge_stages(2) {}
|
||||
set merge_stages(3) {}
|
||||
set merge_stages_buf {}
|
||||
|
||||
set merge_stages_fd [eval git_read ls-files -u -z -- $path]
|
||||
|
||||
fconfigure $merge_stages_fd -blocking 0 -translation binary -encoding binary
|
||||
fileevent $merge_stages_fd readable [list read_merge_stages $merge_stages_fd $cont]
|
||||
}
|
||||
|
||||
proc read_merge_stages {fd cont} {
|
||||
global merge_stages_buf merge_stages_fd merge_stages
|
||||
|
||||
append merge_stages_buf [read $fd]
|
||||
set pck [split $merge_stages_buf "\0"]
|
||||
set merge_stages_buf [lindex $pck end]
|
||||
|
||||
if {[eof $fd] && $merge_stages_buf ne {}} {
|
||||
lappend pck {}
|
||||
set merge_stages_buf {}
|
||||
}
|
||||
|
||||
foreach p [lrange $pck 0 end-1] {
|
||||
set fcols [split $p "\t"]
|
||||
set cols [split [lindex $fcols 0] " "]
|
||||
set stage [lindex $cols 2]
|
||||
|
||||
set merge_stages($stage) [lrange $cols 0 1]
|
||||
}
|
||||
|
||||
if {[eof $fd]} {
|
||||
close $fd
|
||||
unset merge_stages_fd
|
||||
eval $cont
|
||||
}
|
||||
}
|
||||
|
||||
proc merge_resolve_tool {} {
|
||||
global current_diff_path
|
||||
|
||||
merge_load_stages $current_diff_path [list merge_resolve_tool2]
|
||||
}
|
||||
|
||||
proc merge_resolve_tool2 {} {
|
||||
global current_diff_path merge_stages
|
||||
|
||||
# Validate the stages
|
||||
if {$merge_stages(2) eq {} ||
|
||||
[lindex $merge_stages(2) 0] eq {120000} ||
|
||||
[lindex $merge_stages(2) 0] eq {160000} ||
|
||||
$merge_stages(3) eq {} ||
|
||||
[lindex $merge_stages(3) 0] eq {120000} ||
|
||||
[lindex $merge_stages(3) 0] eq {160000}
|
||||
} {
|
||||
error_popup [mc "Cannot resolve deletion or link conflicts using a tool"]
|
||||
return
|
||||
}
|
||||
|
||||
if {![file exists $current_diff_path]} {
|
||||
error_popup [mc "Conflict file does not exist"]
|
||||
return
|
||||
}
|
||||
|
||||
# Determine the tool to use
|
||||
set tool [get_config merge.tool]
|
||||
if {$tool eq {}} { set tool meld }
|
||||
|
||||
set merge_tool_path [get_config "mergetool.$tool.path"]
|
||||
if {$merge_tool_path eq {}} {
|
||||
switch -- $tool {
|
||||
emerge { set merge_tool_path "emacs" }
|
||||
araxis { set merge_tool_path "compare" }
|
||||
default { set merge_tool_path $tool }
|
||||
}
|
||||
}
|
||||
|
||||
# Make file names
|
||||
set filebase [file rootname $current_diff_path]
|
||||
set fileext [file extension $current_diff_path]
|
||||
set basename [lindex [file split $current_diff_path] end]
|
||||
|
||||
set MERGED $current_diff_path
|
||||
set BASE "./$MERGED.BASE$fileext"
|
||||
set LOCAL "./$MERGED.LOCAL$fileext"
|
||||
set REMOTE "./$MERGED.REMOTE$fileext"
|
||||
set BACKUP "./$MERGED.BACKUP$fileext"
|
||||
|
||||
set base_stage $merge_stages(1)
|
||||
|
||||
# Build the command line
|
||||
switch -- $tool {
|
||||
kdiff3 {
|
||||
if {$base_stage ne {}} {
|
||||
set cmdline [list "$merge_tool_path" --auto --L1 "$MERGED (Base)" \
|
||||
--L2 "$MERGED (Local)" --L3 "$MERGED (Remote)" -o "$MERGED" "$BASE" "$LOCAL" "$REMOTE"]
|
||||
} else {
|
||||
set cmdline [list "$merge_tool_path" --auto --L1 "$MERGED (Local)" \
|
||||
--L2 "$MERGED (Remote)" -o "$MERGED" "$LOCAL" "$REMOTE"]
|
||||
}
|
||||
}
|
||||
tkdiff {
|
||||
if {$base_stage ne {}} {
|
||||
set cmdline [list "$merge_tool_path" -a "$BASE" -o "$MERGED" "$LOCAL" "$REMOTE"]
|
||||
} else {
|
||||
set cmdline [list "$merge_tool_path" -o "$MERGED" "$LOCAL" "$REMOTE"]
|
||||
}
|
||||
}
|
||||
meld {
|
||||
set cmdline [list "$merge_tool_path" "$LOCAL" "$MERGED" "$REMOTE"]
|
||||
}
|
||||
gvimdiff {
|
||||
set cmdline [list "$merge_tool_path" -f "$LOCAL" "$MERGED" "$REMOTE"]
|
||||
}
|
||||
xxdiff {
|
||||
if {$base_stage ne {}} {
|
||||
set cmdline [list "$merge_tool_path" -X --show-merged-pane \
|
||||
-R {Accel.SaveAsMerged: "Ctrl-S"} \
|
||||
-R {Accel.Search: "Ctrl+F"} \
|
||||
-R {Accel.SearchForward: "Ctrl-G"} \
|
||||
--merged-file "$MERGED" "$LOCAL" "$BASE" "$REMOTE"]
|
||||
} else {
|
||||
set cmdline [list "$merge_tool_path" -X --show-merged-pane \
|
||||
-R {Accel.SaveAsMerged: "Ctrl-S"} \
|
||||
-R {Accel.Search: "Ctrl+F"} \
|
||||
-R {Accel.SearchForward: "Ctrl-G"} \
|
||||
--merged-file "$MERGED" "$LOCAL" "$REMOTE"]
|
||||
}
|
||||
}
|
||||
opendiff {
|
||||
if {$base_stage ne {}} {
|
||||
set cmdline [list "$merge_tool_path" "$LOCAL" "$REMOTE" -ancestor "$BASE" -merge "$MERGED"]
|
||||
} else {
|
||||
set cmdline [list "$merge_tool_path" "$LOCAL" "$REMOTE" -merge "$MERGED"]
|
||||
}
|
||||
}
|
||||
ecmerge {
|
||||
if {$base_stage ne {}} {
|
||||
set cmdline [list "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" --default --mode=merge3 --to="$MERGED"]
|
||||
} else {
|
||||
set cmdline [list "$merge_tool_path" "$LOCAL" "$REMOTE" --default --mode=merge2 --to="$MERGED"]
|
||||
}
|
||||
}
|
||||
emerge {
|
||||
if {$base_stage ne {}} {
|
||||
set cmdline [list "$merge_tool_path" -f emerge-files-with-ancestor-command \
|
||||
"$LOCAL" "$REMOTE" "$BASE" "$basename"]
|
||||
} else {
|
||||
set cmdline [list "$merge_tool_path" -f emerge-files-command \
|
||||
"$LOCAL" "$REMOTE" "$basename"]
|
||||
}
|
||||
}
|
||||
winmerge {
|
||||
if {$base_stage ne {}} {
|
||||
# This tool does not support 3-way merges.
|
||||
# Use the 'conflict file' resolution feature instead.
|
||||
set cmdline [list "$merge_tool_path" -e -ub "$MERGED"]
|
||||
} else {
|
||||
set cmdline [list "$merge_tool_path" -e -ub -wl \
|
||||
-dl "Theirs File" -dr "Mine File" "$REMOTE" "$LOCAL" "$MERGED"]
|
||||
}
|
||||
}
|
||||
araxis {
|
||||
if {$base_stage ne {}} {
|
||||
set cmdline [list "$merge_tool_path" -wait -merge -3 -a1 \
|
||||
-title1:"'$MERGED (Base)'" -title2:"'$MERGED (Local)'" \
|
||||
-title3:"'$MERGED (Remote)'" \
|
||||
"$BASE" "$LOCAL" "$REMOTE" "$MERGED"]
|
||||
} else {
|
||||
set cmdline [list "$merge_tool_path" -wait -2 \
|
||||
-title1:"'$MERGED (Local)'" -title2:"'$MERGED (Remote)'" \
|
||||
"$LOCAL" "$REMOTE" "$MERGED"]
|
||||
}
|
||||
}
|
||||
p4merge {
|
||||
set cmdline [list "$merge_tool_path" "$BASE" "$REMOTE" "$LOCAL" "$MERGED"]
|
||||
}
|
||||
vimdiff {
|
||||
error_popup [mc "Not a GUI merge tool: '%s'" $tool]
|
||||
return
|
||||
}
|
||||
default {
|
||||
error_popup [mc "Unsupported merge tool '%s'" $tool]
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
merge_tool_start $cmdline $MERGED $BACKUP [list $BASE $LOCAL $REMOTE]
|
||||
}
|
||||
|
||||
proc delete_temp_files {files} {
|
||||
foreach fname $files {
|
||||
file delete $fname
|
||||
}
|
||||
}
|
||||
|
||||
proc merge_tool_get_stages {target stages} {
|
||||
global merge_stages
|
||||
|
||||
set i 1
|
||||
foreach fname $stages {
|
||||
if {$merge_stages($i) eq {}} {
|
||||
file delete $fname
|
||||
catch { close [open $fname w] }
|
||||
} else {
|
||||
# A hack to support autocrlf properly
|
||||
git checkout-index -f --stage=$i -- $target
|
||||
file rename -force -- $target $fname
|
||||
}
|
||||
incr i
|
||||
}
|
||||
}
|
||||
|
||||
proc merge_tool_start {cmdline target backup stages} {
|
||||
global merge_stages mtool_target mtool_tmpfiles mtool_fd mtool_mtime
|
||||
|
||||
if {[info exists mtool_fd]} {
|
||||
if {[ask_popup [mc "Merge tool is already running, terminate it?"]] eq {yes}} {
|
||||
catch { kill_file_process $mtool_fd }
|
||||
catch { close $mtool_fd }
|
||||
unset mtool_fd
|
||||
|
||||
set old_backup [lindex $mtool_tmpfiles end]
|
||||
file rename -force -- $old_backup $mtool_target
|
||||
delete_temp_files $mtool_tmpfiles
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
# Save the original file
|
||||
file rename -force -- $target $backup
|
||||
|
||||
# Get the blobs; it destroys $target
|
||||
if {[catch {merge_tool_get_stages $target $stages} err]} {
|
||||
file rename -force -- $backup $target
|
||||
delete_temp_files $stages
|
||||
error_popup [mc "Error retrieving versions:\n%s" $err]
|
||||
return
|
||||
}
|
||||
|
||||
# Restore the conflict file
|
||||
file copy -force -- $backup $target
|
||||
|
||||
# Initialize global state
|
||||
set mtool_target $target
|
||||
set mtool_mtime [file mtime $target]
|
||||
set mtool_tmpfiles $stages
|
||||
|
||||
lappend mtool_tmpfiles $backup
|
||||
|
||||
# Force redirection to avoid interpreting output on stderr
|
||||
# as an error, and launch the tool
|
||||
lappend cmdline {2>@1}
|
||||
|
||||
if {[catch { set mtool_fd [_open_stdout_stderr $cmdline] } err]} {
|
||||
delete_temp_files $mtool_tmpfiles
|
||||
error_popup [mc "Could not start the merge tool:\n\n%s" $err]
|
||||
return
|
||||
}
|
||||
|
||||
ui_status [mc "Running merge tool..."]
|
||||
|
||||
fconfigure $mtool_fd -blocking 0 -translation binary -encoding binary
|
||||
fileevent $mtool_fd readable [list read_mtool_output $mtool_fd]
|
||||
}
|
||||
|
||||
proc read_mtool_output {fd} {
|
||||
global mtool_fd mtool_tmpfiles
|
||||
|
||||
read $fd
|
||||
if {[eof $fd]} {
|
||||
unset mtool_fd
|
||||
|
||||
fconfigure $fd -blocking 1
|
||||
merge_tool_finish $fd
|
||||
}
|
||||
}
|
||||
|
||||
proc merge_tool_finish {fd} {
|
||||
global mtool_tmpfiles mtool_target mtool_mtime
|
||||
|
||||
set backup [lindex $mtool_tmpfiles end]
|
||||
set failed 0
|
||||
|
||||
# Check the return code
|
||||
if {[catch {close $fd} err]} {
|
||||
set failed 1
|
||||
if {$err ne {child process exited abnormally}} {
|
||||
error_popup [strcat [mc "Merge tool failed."] "\n\n$err"]
|
||||
}
|
||||
}
|
||||
|
||||
# Check the modification time of the target file
|
||||
if {!$failed && [file mtime $mtool_target] eq $mtool_mtime} {
|
||||
if {[ask_popup [mc "File %s unchanged, still accept as resolved?" \
|
||||
[short_path $mtool_target]]] ne {yes}} {
|
||||
set failed 1
|
||||
}
|
||||
}
|
||||
|
||||
# Finish
|
||||
if {$failed} {
|
||||
file rename -force -- $backup $mtool_target
|
||||
delete_temp_files $mtool_tmpfiles
|
||||
ui_status [mc "Merge tool failed."]
|
||||
} else {
|
||||
if {[is_config_true merge.keepbackup]} {
|
||||
file rename -force -- $backup "$mtool_target.orig"
|
||||
}
|
||||
|
||||
delete_temp_files $mtool_tmpfiles
|
||||
|
||||
merge_add_resolution $mtool_target
|
||||
}
|
||||
}
|
||||
@@ -119,13 +119,15 @@ proc do_options {} {
|
||||
{b merge.summary {mc "Summarize Merge Commits"}}
|
||||
{i-1..5 merge.verbosity {mc "Merge Verbosity"}}
|
||||
{b merge.diffstat {mc "Show Diffstat After Merge"}}
|
||||
{t merge.tool {mc "Use Merge Tool"}}
|
||||
|
||||
{b gui.trustmtime {mc "Trust File Modification Timestamps"}}
|
||||
{b gui.pruneduringfetch {mc "Prune Tracking Branches During Fetch"}}
|
||||
{b gui.matchtrackingbranch {mc "Match Tracking Branches"}}
|
||||
{b gui.fastcopyblame {mc "Blame Copy Only On Changed Files"}}
|
||||
{i-20..200 gui.copyblamethreshold {mc "Minimum Letters To Blame Copy On"}}
|
||||
{i-0..99 gui.diffcontext {mc "Number of Diff Context Lines"}}
|
||||
{i-0..300 gui.blamehistoryctx {mc "Blame History Context Radius (days)"}}
|
||||
{i-1..99 gui.diffcontext {mc "Number of Diff Context Lines"}}
|
||||
{i-0..99 gui.commitmsgwidth {mc "Commit Message Text Width"}}
|
||||
{t gui.newbranchtemplate {mc "New Branch Name Template"}}
|
||||
} {
|
||||
|
||||
918
git-gui/po/fr.po
918
git-gui/po/fr.po
File diff suppressed because it is too large
Load Diff
@@ -11,8 +11,8 @@ proc u2a {s} {
|
||||
foreach i [split $s ""] {
|
||||
scan $i %c c
|
||||
if {$c<128} {
|
||||
# escape '[', '\' and ']'
|
||||
if {$c == 0x5b || $c == 0x5d} {
|
||||
# escape '[', '\', '$' and ']'
|
||||
if {$c == 0x5b || $c == 0x5d || $c == 0x24} {
|
||||
append res "\\"
|
||||
}
|
||||
append res $i
|
||||
|
||||
@@ -39,6 +39,7 @@ clear_stash () {
|
||||
create_stash () {
|
||||
stash_msg="$1"
|
||||
|
||||
git update-index -q --refresh
|
||||
if no_changes
|
||||
then
|
||||
exit 0
|
||||
@@ -101,6 +102,7 @@ save_stash () {
|
||||
|
||||
stash_msg="$*"
|
||||
|
||||
git update-index -q --refresh
|
||||
if no_changes
|
||||
then
|
||||
echo 'No local changes to save'
|
||||
@@ -150,6 +152,7 @@ show_stash () {
|
||||
}
|
||||
|
||||
apply_stash () {
|
||||
git update-index -q --refresh &&
|
||||
git diff-files --quiet --ignore-submodules ||
|
||||
die 'Cannot restore on top of a dirty state'
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
USAGE="[--quiet] [--cached] \
|
||||
[add <repo> [-b branch] <path>]|[status|init|update [-i|--init]|summary [-n|--summary-limit <n>] [<commit>]] \
|
||||
[--] [<path>...]|[foreach <command>]"
|
||||
[--] [<path>...]|[foreach <command>]|[sync [--] [<path>...]]"
|
||||
OPTIONS_SPEC=
|
||||
. git-sh-setup
|
||||
. git-parse-remote
|
||||
@@ -35,6 +35,7 @@ resolve_relative_url ()
|
||||
remoteurl=$(git config "remote.$remote.url") ||
|
||||
die "remote ($remote) does not have a url defined in .git/config"
|
||||
url="$1"
|
||||
remoteurl=${remoteurl%/}
|
||||
while test -n "$url"
|
||||
do
|
||||
case "$url" in
|
||||
@@ -49,7 +50,7 @@ resolve_relative_url ()
|
||||
break;;
|
||||
esac
|
||||
done
|
||||
echo "$remoteurl/$url"
|
||||
echo "$remoteurl/${url%/}"
|
||||
}
|
||||
|
||||
#
|
||||
@@ -601,6 +602,50 @@ cmd_status()
|
||||
fi
|
||||
done
|
||||
}
|
||||
#
|
||||
# Sync remote urls for submodules
|
||||
# This makes the value for remote.$remote.url match the value
|
||||
# specified in .gitmodules.
|
||||
#
|
||||
cmd_sync()
|
||||
{
|
||||
while test $# -ne 0
|
||||
do
|
||||
case "$1" in
|
||||
-q|--quiet)
|
||||
quiet=1
|
||||
shift
|
||||
;;
|
||||
--)
|
||||
shift
|
||||
break
|
||||
;;
|
||||
-*)
|
||||
usage
|
||||
;;
|
||||
*)
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
cd_to_toplevel
|
||||
module_list "$@" |
|
||||
while read mode sha1 stage path
|
||||
do
|
||||
name=$(module_name "$path")
|
||||
url=$(git config -f .gitmodules --get submodule."$name".url)
|
||||
if test -e "$path"/.git
|
||||
then
|
||||
(
|
||||
unset GIT_DIR
|
||||
cd "$path"
|
||||
remote=$(get_default_remote)
|
||||
say "Synchronizing submodule url for '$name'"
|
||||
git config remote."$remote".url "$url"
|
||||
)
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# This loop parses the command line arguments to find the
|
||||
# subcommand name to dispatch. Parsing of the subcommand specific
|
||||
@@ -611,7 +656,7 @@ cmd_status()
|
||||
while test $# != 0 && test -z "$command"
|
||||
do
|
||||
case "$1" in
|
||||
add | foreach | init | update | status | summary)
|
||||
add | foreach | init | update | status | summary | sync)
|
||||
command=$1
|
||||
;;
|
||||
-q|--quiet)
|
||||
|
||||
107
git-svn.perl
107
git-svn.perl
@@ -421,15 +421,15 @@ sub cmd_dcommit {
|
||||
$head ||= 'HEAD';
|
||||
my @refs;
|
||||
my ($url, $rev, $uuid, $gs) = working_head_info($head, \@refs);
|
||||
unless ($gs) {
|
||||
die "Unable to determine upstream SVN information from ",
|
||||
"$head history.\nPerhaps the repository is empty.";
|
||||
}
|
||||
$url = defined $_commit_url ? $_commit_url : $gs->full_url;
|
||||
my $last_rev = $_revision if defined $_revision;
|
||||
if ($url) {
|
||||
print "Committing to $url ...\n";
|
||||
}
|
||||
unless ($gs) {
|
||||
die "Unable to determine upstream SVN information from ",
|
||||
"$head history.\nPerhaps the repository is empty.";
|
||||
}
|
||||
my ($linear_refs, $parents) = linearize_history($gs, \@refs);
|
||||
if ($_no_rebase && scalar(@$linear_refs) > 1) {
|
||||
warn "Attempting to commit more than one change while ",
|
||||
@@ -803,8 +803,28 @@ sub cmd_commit_diff {
|
||||
}
|
||||
}
|
||||
|
||||
sub escape_uri_only {
|
||||
my ($uri) = @_;
|
||||
my @tmp;
|
||||
foreach (split m{/}, $uri) {
|
||||
s/([^\w.%+-]|%(?![a-fA-F0-9]{2}))/sprintf("%%%02X",ord($1))/eg;
|
||||
push @tmp, $_;
|
||||
}
|
||||
join('/', @tmp);
|
||||
}
|
||||
|
||||
sub escape_url {
|
||||
my ($url) = @_;
|
||||
if ($url =~ m#^([^:]+)://([^/]*)(.*)$#) {
|
||||
my ($scheme, $domain, $uri) = ($1, $2, escape_uri_only($3));
|
||||
$url = "$scheme://$domain$uri";
|
||||
}
|
||||
$url;
|
||||
}
|
||||
|
||||
sub cmd_info {
|
||||
my $path = canonicalize_path(defined($_[0]) ? $_[0] : ".");
|
||||
my $fullpath = canonicalize_path($cmd_dir_prefix . $path);
|
||||
if (exists $_[1]) {
|
||||
die "Too many arguments specified\n";
|
||||
}
|
||||
@@ -812,8 +832,8 @@ sub cmd_info {
|
||||
my ($file_type, $diff_status) = find_file_type_and_diff_status($path);
|
||||
|
||||
if (!$file_type && !$diff_status) {
|
||||
print STDERR "$path: (Not a versioned resource)\n\n";
|
||||
return;
|
||||
print STDERR "svn: '$path' is not under version control\n";
|
||||
exit 1;
|
||||
}
|
||||
|
||||
my ($url, $rev, $uuid, $gs) = working_head_info('HEAD');
|
||||
@@ -825,21 +845,21 @@ sub cmd_info {
|
||||
# canonicalize_path() will return "" to make libsvn 1.5.x happy,
|
||||
$path = "." if $path eq "";
|
||||
|
||||
my $full_url = $url . ($path eq "." ? "" : "/$path");
|
||||
my $full_url = $url . ($fullpath eq "" ? "" : "/$fullpath");
|
||||
|
||||
if ($_url) {
|
||||
print $full_url, "\n";
|
||||
print escape_url($full_url), "\n";
|
||||
return;
|
||||
}
|
||||
|
||||
my $result = "Path: $path\n";
|
||||
$result .= "Name: " . basename($path) . "\n" if $file_type ne "dir";
|
||||
$result .= "URL: " . $full_url . "\n";
|
||||
$result .= "URL: " . escape_url($full_url) . "\n";
|
||||
|
||||
eval {
|
||||
my $repos_root = $gs->repos_root;
|
||||
Git::SVN::remove_username($repos_root);
|
||||
$result .= "Repository Root: $repos_root\n";
|
||||
$result .= "Repository Root: " . escape_url($repos_root) . "\n";
|
||||
};
|
||||
if ($@) {
|
||||
$result .= "Repository Root: (offline)\n";
|
||||
@@ -861,7 +881,7 @@ sub cmd_info {
|
||||
}
|
||||
|
||||
my ($lc_author, $lc_rev, $lc_date_utc);
|
||||
my @args = Git::SVN::Log::git_svn_log_cmd($rev, $rev, "--", $path);
|
||||
my @args = Git::SVN::Log::git_svn_log_cmd($rev, $rev, "--", $fullpath);
|
||||
my $log = command_output_pipe(@args);
|
||||
my $esc_color = qr/(?:\033\[(?:(?:\d+;)*\d*)?m)*/;
|
||||
while (<$log>) {
|
||||
@@ -3380,11 +3400,12 @@ sub generate_diff {
|
||||
while (<$diff_fh>) {
|
||||
chomp $_; # this gets rid of the trailing "\0"
|
||||
if ($state eq 'meta' && /^:(\d{6})\s(\d{6})\s
|
||||
$::sha1\s($::sha1)\s
|
||||
($::sha1)\s($::sha1)\s
|
||||
([MTCRAD])\d*$/xo) {
|
||||
push @mods, { mode_a => $1, mode_b => $2,
|
||||
sha1_b => $3, chg => $4 };
|
||||
if ($4 =~ /^(?:C|R)$/) {
|
||||
sha1_a => $3, sha1_b => $4,
|
||||
chg => $5 };
|
||||
if ($5 =~ /^(?:C|R)$/) {
|
||||
$state = 'file_a';
|
||||
} else {
|
||||
$state = 'file_b';
|
||||
@@ -3636,6 +3657,7 @@ sub R {
|
||||
my $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat,
|
||||
$self->url_path($m->{file_a}), $self->{r});
|
||||
print "\tR\t$m->{file_a} => $m->{file_b}\n" unless $::_q;
|
||||
$self->apply_autoprops($file, $fbat);
|
||||
$self->chg_file($fbat, $m);
|
||||
$self->close_file($fbat,undef,$self->{pool});
|
||||
|
||||
@@ -3662,6 +3684,27 @@ sub change_file_prop {
|
||||
$self->SUPER::change_file_prop($fbat, $pname, $pval, $self->{pool});
|
||||
}
|
||||
|
||||
sub _chg_file_get_blob ($$$$) {
|
||||
my ($self, $fbat, $m, $which) = @_;
|
||||
my $fh = Git::temp_acquire("git_blob_$which");
|
||||
if ($m->{"mode_$which"} =~ /^120/) {
|
||||
print $fh 'link ' or croak $!;
|
||||
$self->change_file_prop($fbat,'svn:special','*');
|
||||
} elsif ($m->{mode_a} =~ /^120/ && $m->{"mode_$which"} !~ /^120/) {
|
||||
$self->change_file_prop($fbat,'svn:special',undef);
|
||||
}
|
||||
my $blob = $m->{"sha1_$which"};
|
||||
return ($fh,) if ($blob =~ /^0{40}$/);
|
||||
my $size = $::_repository->cat_blob($blob, $fh);
|
||||
croak "Failed to read object $blob" if ($size < 0);
|
||||
$fh->flush == 0 or croak $!;
|
||||
seek $fh, 0, 0 or croak $!;
|
||||
|
||||
my $exp = ::md5sum($fh);
|
||||
seek $fh, 0, 0 or croak $!;
|
||||
return ($fh, $exp);
|
||||
}
|
||||
|
||||
sub chg_file {
|
||||
my ($self, $fbat, $m) = @_;
|
||||
if ($m->{mode_b} =~ /755$/ && $m->{mode_a} !~ /755$/) {
|
||||
@@ -3669,26 +3712,24 @@ sub chg_file {
|
||||
} elsif ($m->{mode_b} !~ /755$/ && $m->{mode_a} =~ /755$/) {
|
||||
$self->change_file_prop($fbat,'svn:executable',undef);
|
||||
}
|
||||
my $fh = Git::temp_acquire('git_blob');
|
||||
if ($m->{mode_b} =~ /^120/) {
|
||||
print $fh 'link ' or croak $!;
|
||||
$self->change_file_prop($fbat,'svn:special','*');
|
||||
} elsif ($m->{mode_a} =~ /^120/ && $m->{mode_b} !~ /^120/) {
|
||||
$self->change_file_prop($fbat,'svn:special',undef);
|
||||
}
|
||||
my $size = $::_repository->cat_blob($m->{sha1_b}, $fh);
|
||||
croak "Failed to read object $m->{sha1_b}" if ($size < 0);
|
||||
$fh->flush == 0 or croak $!;
|
||||
seek $fh, 0, 0 or croak $!;
|
||||
|
||||
my $exp = ::md5sum($fh);
|
||||
seek $fh, 0, 0 or croak $!;
|
||||
|
||||
my ($fh_a, $exp_a) = _chg_file_get_blob $self, $fbat, $m, 'a';
|
||||
my ($fh_b, $exp_b) = _chg_file_get_blob $self, $fbat, $m, 'b';
|
||||
my $pool = SVN::Pool->new;
|
||||
my $atd = $self->apply_textdelta($fbat, undef, $pool);
|
||||
my $got = SVN::TxDelta::send_stream($fh, @$atd, $pool);
|
||||
die "Checksum mismatch\nexpected: $exp\ngot: $got\n" if ($got ne $exp);
|
||||
Git::temp_release($fh, 1);
|
||||
my $atd = $self->apply_textdelta($fbat, $exp_a, $pool);
|
||||
if (-s $fh_a) {
|
||||
my $txstream = SVN::TxDelta::new ($fh_a, $fh_b, $pool);
|
||||
my $res = SVN::TxDelta::send_txstream($txstream, @$atd, $pool);
|
||||
if (defined $res) {
|
||||
die "Unexpected result from send_txstream: $res\n",
|
||||
"(SVN::Core::VERSION: $SVN::Core::VERSION)\n";
|
||||
}
|
||||
} else {
|
||||
my $got = SVN::TxDelta::send_stream($fh_b, @$atd, $pool);
|
||||
die "Checksum mismatch\nexpected: $exp_b\ngot: $got\n"
|
||||
if ($got ne $exp_b);
|
||||
}
|
||||
Git::temp_release($fh_b, 1);
|
||||
Git::temp_release($fh_a, 1);
|
||||
$pool->clear;
|
||||
}
|
||||
|
||||
|
||||
2
git.c
2
git.c
@@ -286,7 +286,7 @@ static void handle_internal_command(int argc, const char **argv)
|
||||
{ "count-objects", cmd_count_objects, RUN_SETUP },
|
||||
{ "describe", cmd_describe, RUN_SETUP },
|
||||
{ "diff", cmd_diff },
|
||||
{ "diff-files", cmd_diff_files, RUN_SETUP },
|
||||
{ "diff-files", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE },
|
||||
{ "diff-index", cmd_diff_index, RUN_SETUP },
|
||||
{ "diff-tree", cmd_diff_tree, RUN_SETUP },
|
||||
{ "fast-export", cmd_fast_export, RUN_SETUP },
|
||||
|
||||
@@ -418,10 +418,12 @@ proc stop_rev_list {view} {
|
||||
}
|
||||
|
||||
proc reset_pending_select {selid} {
|
||||
global pending_select mainheadid
|
||||
global pending_select mainheadid selectheadid
|
||||
|
||||
if {$selid ne {}} {
|
||||
set pending_select $selid
|
||||
} elseif {$selectheadid ne {}} {
|
||||
set pending_select $selectheadid
|
||||
} else {
|
||||
set pending_select $mainheadid
|
||||
}
|
||||
@@ -1609,6 +1611,7 @@ proc getcommit {id} {
|
||||
proc readrefs {} {
|
||||
global tagids idtags headids idheads tagobjid
|
||||
global otherrefids idotherrefs mainhead mainheadid
|
||||
global selecthead selectheadid
|
||||
|
||||
foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
|
||||
catch {unset $v}
|
||||
@@ -1655,6 +1658,12 @@ proc readrefs {} {
|
||||
set mainhead [string range $thehead 11 end]
|
||||
}
|
||||
}
|
||||
set selectheadid {}
|
||||
if {$selecthead ne {}} {
|
||||
catch {
|
||||
set selectheadid [exec git rev-parse --verify $selecthead]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# skip over fake commits
|
||||
@@ -2205,6 +2214,8 @@ proc makewindow {} {
|
||||
-command {flist_hl 1}
|
||||
$flist_menu add command -label [mc "External diff"] \
|
||||
-command {external_diff}
|
||||
$flist_menu add command -label [mc "Blame parent commit"] \
|
||||
-command {external_blame 1}
|
||||
}
|
||||
|
||||
# Windows sends all mouse wheel events to the current focused window, not
|
||||
@@ -3013,6 +3024,27 @@ proc external_diff {} {
|
||||
}
|
||||
}
|
||||
|
||||
proc external_blame {parent_idx} {
|
||||
global flist_menu_file
|
||||
global nullid nullid2
|
||||
global parentlist selectedline currentid
|
||||
|
||||
if {$parent_idx > 0} {
|
||||
set base_commit [lindex $parentlist $selectedline [expr {$parent_idx-1}]]
|
||||
} else {
|
||||
set base_commit $currentid
|
||||
}
|
||||
|
||||
if {$base_commit eq {} || $base_commit eq $nullid || $base_commit eq $nullid2} {
|
||||
error_popup [mc "No such commit"]
|
||||
return
|
||||
}
|
||||
|
||||
if {[catch {exec git gui blame $base_commit $flist_menu_file &} err]} {
|
||||
error_popup [mc "git gui blame: command failed: $err"]
|
||||
}
|
||||
}
|
||||
|
||||
# delete $dir when we see eof on $f (presumably because the child has exited)
|
||||
proc delete_at_eof {f dir} {
|
||||
while {[gets $f line] >= 0} {}
|
||||
@@ -9866,6 +9898,9 @@ if {![file isdirectory $gitdir]} {
|
||||
exit 1
|
||||
}
|
||||
|
||||
set selecthead {}
|
||||
set selectheadid {}
|
||||
|
||||
set revtreeargs {}
|
||||
set cmdline_files {}
|
||||
set i 0
|
||||
@@ -9877,6 +9912,9 @@ foreach arg $argv {
|
||||
set cmdline_files [lrange $argv [expr {$i + 1}] end]
|
||||
break
|
||||
}
|
||||
"--select-commit=*" {
|
||||
set selecthead [string range $arg 16 end]
|
||||
}
|
||||
"--argscmd=*" {
|
||||
set revtreeargscmd [string range $arg 10 end]
|
||||
}
|
||||
@@ -9887,6 +9925,10 @@ foreach arg $argv {
|
||||
incr i
|
||||
}
|
||||
|
||||
if {$selecthead eq "HEAD"} {
|
||||
set selecthead {}
|
||||
}
|
||||
|
||||
if {$i >= [llength $argv] && $revtreeargs ne {}} {
|
||||
# no -- on command line, but some arguments (other than --argscmd)
|
||||
if {[catch {
|
||||
|
||||
@@ -481,6 +481,19 @@ span.refs span {
|
||||
border-color: #ffccff #ff00ee #ff00ee #ffccff;
|
||||
}
|
||||
|
||||
span.refs span a {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
span.refs span a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
span.refs span.indirect {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
span.refs span.ref {
|
||||
background-color: #aaaaff;
|
||||
border-color: #ccccff #0033cc #0033cc #ccccff;
|
||||
|
||||
@@ -1090,13 +1090,23 @@ sub format_log_line_html {
|
||||
}
|
||||
|
||||
# format marker of refs pointing to given object
|
||||
|
||||
# the destination action is chosen based on object type and current context:
|
||||
# - for annotated tags, we choose the tag view unless it's the current view
|
||||
# already, in which case we go to shortlog view
|
||||
# - for other refs, we keep the current view if we're in history, shortlog or
|
||||
# log view, and select shortlog otherwise
|
||||
sub format_ref_marker {
|
||||
my ($refs, $id) = @_;
|
||||
my $markers = '';
|
||||
|
||||
if (defined $refs->{$id}) {
|
||||
foreach my $ref (@{$refs->{$id}}) {
|
||||
# this code exploits the fact that non-lightweight tags are the
|
||||
# only indirect objects, and that they are the only objects for which
|
||||
# we want to use tag instead of shortlog as action
|
||||
my ($type, $name) = qw();
|
||||
my $indirect = ($ref =~ s/\^\{\}$//);
|
||||
# e.g. tags/v2.6.11 or heads/next
|
||||
if ($ref =~ m!^(.*?)s?/(.*)$!) {
|
||||
$type = $1;
|
||||
@@ -1106,8 +1116,29 @@ sub format_ref_marker {
|
||||
$name = $ref;
|
||||
}
|
||||
|
||||
$markers .= " <span class=\"$type\" title=\"$ref\">" .
|
||||
esc_html($name) . "</span>";
|
||||
my $class = $type;
|
||||
$class .= " indirect" if $indirect;
|
||||
|
||||
my $dest_action = "shortlog";
|
||||
|
||||
if ($indirect) {
|
||||
$dest_action = "tag" unless $action eq "tag";
|
||||
} elsif ($action =~ /^(history|(short)?log)$/) {
|
||||
$dest_action = $action;
|
||||
}
|
||||
|
||||
my $dest = "";
|
||||
$dest .= "refs/" unless $ref =~ m!^refs/!;
|
||||
$dest .= $ref;
|
||||
|
||||
my $link = $cgi->a({
|
||||
-href => href(
|
||||
action=>$dest_action,
|
||||
hash=>$dest
|
||||
)}, $name);
|
||||
|
||||
$markers .= " <span class=\"$class\" title=\"$ref\">" .
|
||||
$link . "</span>";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1918,7 +1949,7 @@ sub git_get_references {
|
||||
|
||||
while (my $line = <$fd>) {
|
||||
chomp $line;
|
||||
if ($line =~ m!^([0-9a-fA-F]{40})\srefs/($type/?[^^]+)!) {
|
||||
if ($line =~ m!^([0-9a-fA-F]{40})\srefs/($type.*)$!) {
|
||||
if (defined $refs{$1}) {
|
||||
push @{$refs{$1}}, $2;
|
||||
} else {
|
||||
|
||||
52
grep.c
52
grep.c
@@ -2,6 +2,19 @@
|
||||
#include "grep.h"
|
||||
#include "xdiff-interface.h"
|
||||
|
||||
void append_header_grep_pattern(struct grep_opt *opt, enum grep_header_field field, const char *pat)
|
||||
{
|
||||
struct grep_pat *p = xcalloc(1, sizeof(*p));
|
||||
p->pattern = pat;
|
||||
p->origin = "header";
|
||||
p->no = 0;
|
||||
p->token = GREP_PATTERN_HEAD;
|
||||
p->field = field;
|
||||
*opt->pattern_tail = p;
|
||||
opt->pattern_tail = &p->next;
|
||||
p->next = NULL;
|
||||
}
|
||||
|
||||
void append_grep_pattern(struct grep_opt *opt, const char *pat,
|
||||
const char *origin, int no, enum grep_pat_token t)
|
||||
{
|
||||
@@ -247,16 +260,53 @@ static int fixmatch(const char *pattern, char *line, regmatch_t *match)
|
||||
}
|
||||
}
|
||||
|
||||
static int strip_timestamp(char *bol, char **eol_p)
|
||||
{
|
||||
char *eol = *eol_p;
|
||||
int ch;
|
||||
|
||||
while (bol < --eol) {
|
||||
if (*eol != '>')
|
||||
continue;
|
||||
*eol_p = ++eol;
|
||||
ch = *eol;
|
||||
*eol = '\0';
|
||||
return ch;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct {
|
||||
const char *field;
|
||||
size_t len;
|
||||
} header_field[] = {
|
||||
{ "author ", 7 },
|
||||
{ "committer ", 10 },
|
||||
};
|
||||
|
||||
static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol, char *eol, enum grep_context ctx)
|
||||
{
|
||||
int hit = 0;
|
||||
int at_true_bol = 1;
|
||||
int saved_ch = 0;
|
||||
regmatch_t pmatch[10];
|
||||
|
||||
if ((p->token != GREP_PATTERN) &&
|
||||
((p->token == GREP_PATTERN_HEAD) != (ctx == GREP_CONTEXT_HEAD)))
|
||||
return 0;
|
||||
|
||||
if (p->token == GREP_PATTERN_HEAD) {
|
||||
const char *field;
|
||||
size_t len;
|
||||
assert(p->field < ARRAY_SIZE(header_field));
|
||||
field = header_field[p->field].field;
|
||||
len = header_field[p->field].len;
|
||||
if (strncmp(bol, field, len))
|
||||
return 0;
|
||||
bol += len;
|
||||
saved_ch = strip_timestamp(bol, &eol);
|
||||
}
|
||||
|
||||
again:
|
||||
if (!opt->fixed) {
|
||||
regex_t *exp = &p->regexp;
|
||||
@@ -298,6 +348,8 @@ static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol
|
||||
goto again;
|
||||
}
|
||||
}
|
||||
if (p->token == GREP_PATTERN_HEAD && saved_ch)
|
||||
*eol = saved_ch;
|
||||
return hit;
|
||||
}
|
||||
|
||||
|
||||
7
grep.h
7
grep.h
@@ -17,12 +17,18 @@ enum grep_context {
|
||||
GREP_CONTEXT_BODY,
|
||||
};
|
||||
|
||||
enum grep_header_field {
|
||||
GREP_HEADER_AUTHOR = 0,
|
||||
GREP_HEADER_COMMITTER,
|
||||
};
|
||||
|
||||
struct grep_pat {
|
||||
struct grep_pat *next;
|
||||
const char *origin;
|
||||
int no;
|
||||
enum grep_pat_token token;
|
||||
const char *pattern;
|
||||
enum grep_header_field field;
|
||||
regex_t regexp;
|
||||
};
|
||||
|
||||
@@ -74,6 +80,7 @@ struct grep_opt {
|
||||
};
|
||||
|
||||
extern void append_grep_pattern(struct grep_opt *opt, const char *pat, const char *origin, int no, enum grep_pat_token t);
|
||||
extern void append_header_grep_pattern(struct grep_opt *, enum grep_header_field, const char *);
|
||||
extern void compile_grep_patterns(struct grep_opt *opt);
|
||||
extern void free_grep_patterns(struct grep_opt *opt);
|
||||
extern int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long size);
|
||||
|
||||
595
help.c
595
help.c
@@ -1,276 +1,7 @@
|
||||
/*
|
||||
* builtin-help.c
|
||||
*
|
||||
* Builtin help-related commands (help, usage, version)
|
||||
*/
|
||||
#include "cache.h"
|
||||
#include "builtin.h"
|
||||
#include "exec_cmd.h"
|
||||
#include "common-cmds.h"
|
||||
#include "parse-options.h"
|
||||
#include "run-command.h"
|
||||
|
||||
static struct man_viewer_list {
|
||||
struct man_viewer_list *next;
|
||||
char name[FLEX_ARRAY];
|
||||
} *man_viewer_list;
|
||||
|
||||
static struct man_viewer_info_list {
|
||||
struct man_viewer_info_list *next;
|
||||
const char *info;
|
||||
char name[FLEX_ARRAY];
|
||||
} *man_viewer_info_list;
|
||||
|
||||
enum help_format {
|
||||
HELP_FORMAT_MAN,
|
||||
HELP_FORMAT_INFO,
|
||||
HELP_FORMAT_WEB,
|
||||
};
|
||||
|
||||
static int show_all = 0;
|
||||
static enum help_format help_format = HELP_FORMAT_MAN;
|
||||
static struct option builtin_help_options[] = {
|
||||
OPT_BOOLEAN('a', "all", &show_all, "print all available commands"),
|
||||
OPT_SET_INT('m', "man", &help_format, "show man page", HELP_FORMAT_MAN),
|
||||
OPT_SET_INT('w', "web", &help_format, "show manual in web browser",
|
||||
HELP_FORMAT_WEB),
|
||||
OPT_SET_INT('i', "info", &help_format, "show info page",
|
||||
HELP_FORMAT_INFO),
|
||||
OPT_END(),
|
||||
};
|
||||
|
||||
static const char * const builtin_help_usage[] = {
|
||||
"git help [--all] [--man|--web|--info] [command]",
|
||||
NULL
|
||||
};
|
||||
|
||||
static enum help_format parse_help_format(const char *format)
|
||||
{
|
||||
if (!strcmp(format, "man"))
|
||||
return HELP_FORMAT_MAN;
|
||||
if (!strcmp(format, "info"))
|
||||
return HELP_FORMAT_INFO;
|
||||
if (!strcmp(format, "web") || !strcmp(format, "html"))
|
||||
return HELP_FORMAT_WEB;
|
||||
die("unrecognized help format '%s'", format);
|
||||
}
|
||||
|
||||
static const char *get_man_viewer_info(const char *name)
|
||||
{
|
||||
struct man_viewer_info_list *viewer;
|
||||
|
||||
for (viewer = man_viewer_info_list; viewer; viewer = viewer->next)
|
||||
{
|
||||
if (!strcasecmp(name, viewer->name))
|
||||
return viewer->info;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int check_emacsclient_version(void)
|
||||
{
|
||||
struct strbuf buffer = STRBUF_INIT;
|
||||
struct child_process ec_process;
|
||||
const char *argv_ec[] = { "emacsclient", "--version", NULL };
|
||||
int version;
|
||||
|
||||
/* emacsclient prints its version number on stderr */
|
||||
memset(&ec_process, 0, sizeof(ec_process));
|
||||
ec_process.argv = argv_ec;
|
||||
ec_process.err = -1;
|
||||
ec_process.stdout_to_stderr = 1;
|
||||
if (start_command(&ec_process)) {
|
||||
fprintf(stderr, "Failed to start emacsclient.\n");
|
||||
return -1;
|
||||
}
|
||||
strbuf_read(&buffer, ec_process.err, 20);
|
||||
close(ec_process.err);
|
||||
|
||||
/*
|
||||
* Don't bother checking return value, because "emacsclient --version"
|
||||
* seems to always exits with code 1.
|
||||
*/
|
||||
finish_command(&ec_process);
|
||||
|
||||
if (prefixcmp(buffer.buf, "emacsclient")) {
|
||||
fprintf(stderr, "Failed to parse emacsclient version.\n");
|
||||
strbuf_release(&buffer);
|
||||
return -1;
|
||||
}
|
||||
|
||||
strbuf_remove(&buffer, 0, strlen("emacsclient"));
|
||||
version = atoi(buffer.buf);
|
||||
|
||||
if (version < 22) {
|
||||
fprintf(stderr,
|
||||
"emacsclient version '%d' too old (< 22).\n",
|
||||
version);
|
||||
strbuf_release(&buffer);
|
||||
return -1;
|
||||
}
|
||||
|
||||
strbuf_release(&buffer);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void exec_woman_emacs(const char* path, const char *page)
|
||||
{
|
||||
if (!check_emacsclient_version()) {
|
||||
/* This works only with emacsclient version >= 22. */
|
||||
struct strbuf man_page = STRBUF_INIT;
|
||||
|
||||
if (!path)
|
||||
path = "emacsclient";
|
||||
strbuf_addf(&man_page, "(woman \"%s\")", page);
|
||||
execlp(path, "emacsclient", "-e", man_page.buf, NULL);
|
||||
warning("failed to exec '%s': %s", path, strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
static void exec_man_konqueror(const char* path, const char *page)
|
||||
{
|
||||
const char *display = getenv("DISPLAY");
|
||||
if (display && *display) {
|
||||
struct strbuf man_page = STRBUF_INIT;
|
||||
const char *filename = "kfmclient";
|
||||
|
||||
/* It's simpler to launch konqueror using kfmclient. */
|
||||
if (path) {
|
||||
const char *file = strrchr(path, '/');
|
||||
if (file && !strcmp(file + 1, "konqueror")) {
|
||||
char *new = xstrdup(path);
|
||||
char *dest = strrchr(new, '/');
|
||||
|
||||
/* strlen("konqueror") == strlen("kfmclient") */
|
||||
strcpy(dest + 1, "kfmclient");
|
||||
path = new;
|
||||
}
|
||||
if (file)
|
||||
filename = file;
|
||||
} else
|
||||
path = "kfmclient";
|
||||
strbuf_addf(&man_page, "man:%s(1)", page);
|
||||
execlp(path, filename, "newTab", man_page.buf, NULL);
|
||||
warning("failed to exec '%s': %s", path, strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
static void exec_man_man(const char* path, const char *page)
|
||||
{
|
||||
if (!path)
|
||||
path = "man";
|
||||
execlp(path, "man", page, NULL);
|
||||
warning("failed to exec '%s': %s", path, strerror(errno));
|
||||
}
|
||||
|
||||
static void exec_man_cmd(const char *cmd, const char *page)
|
||||
{
|
||||
struct strbuf shell_cmd = STRBUF_INIT;
|
||||
strbuf_addf(&shell_cmd, "%s %s", cmd, page);
|
||||
execl("/bin/sh", "sh", "-c", shell_cmd.buf, NULL);
|
||||
warning("failed to exec '%s': %s", cmd, strerror(errno));
|
||||
}
|
||||
|
||||
static void add_man_viewer(const char *name)
|
||||
{
|
||||
struct man_viewer_list **p = &man_viewer_list;
|
||||
size_t len = strlen(name);
|
||||
|
||||
while (*p)
|
||||
p = &((*p)->next);
|
||||
*p = xcalloc(1, (sizeof(**p) + len + 1));
|
||||
strncpy((*p)->name, name, len);
|
||||
}
|
||||
|
||||
static int supported_man_viewer(const char *name, size_t len)
|
||||
{
|
||||
return (!strncasecmp("man", name, len) ||
|
||||
!strncasecmp("woman", name, len) ||
|
||||
!strncasecmp("konqueror", name, len));
|
||||
}
|
||||
|
||||
static void do_add_man_viewer_info(const char *name,
|
||||
size_t len,
|
||||
const char *value)
|
||||
{
|
||||
struct man_viewer_info_list *new = xcalloc(1, sizeof(*new) + len + 1);
|
||||
|
||||
strncpy(new->name, name, len);
|
||||
new->info = xstrdup(value);
|
||||
new->next = man_viewer_info_list;
|
||||
man_viewer_info_list = new;
|
||||
}
|
||||
|
||||
static int add_man_viewer_path(const char *name,
|
||||
size_t len,
|
||||
const char *value)
|
||||
{
|
||||
if (supported_man_viewer(name, len))
|
||||
do_add_man_viewer_info(name, len, value);
|
||||
else
|
||||
warning("'%s': path for unsupported man viewer.\n"
|
||||
"Please consider using 'man.<tool>.cmd' instead.",
|
||||
name);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int add_man_viewer_cmd(const char *name,
|
||||
size_t len,
|
||||
const char *value)
|
||||
{
|
||||
if (supported_man_viewer(name, len))
|
||||
warning("'%s': cmd for supported man viewer.\n"
|
||||
"Please consider using 'man.<tool>.path' instead.",
|
||||
name);
|
||||
else
|
||||
do_add_man_viewer_info(name, len, value);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int add_man_viewer_info(const char *var, const char *value)
|
||||
{
|
||||
const char *name = var + 4;
|
||||
const char *subkey = strrchr(name, '.');
|
||||
|
||||
if (!subkey)
|
||||
return error("Config with no key for man viewer: %s", name);
|
||||
|
||||
if (!strcmp(subkey, ".path")) {
|
||||
if (!value)
|
||||
return config_error_nonbool(var);
|
||||
return add_man_viewer_path(name, subkey - name, value);
|
||||
}
|
||||
if (!strcmp(subkey, ".cmd")) {
|
||||
if (!value)
|
||||
return config_error_nonbool(var);
|
||||
return add_man_viewer_cmd(name, subkey - name, value);
|
||||
}
|
||||
|
||||
warning("'%s': unsupported man viewer sub key.", subkey);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int git_help_config(const char *var, const char *value, void *cb)
|
||||
{
|
||||
if (!strcmp(var, "help.format")) {
|
||||
if (!value)
|
||||
return config_error_nonbool(var);
|
||||
help_format = parse_help_format(value);
|
||||
return 0;
|
||||
}
|
||||
if (!strcmp(var, "man.viewer")) {
|
||||
if (!value)
|
||||
return config_error_nonbool(var);
|
||||
add_man_viewer(value);
|
||||
return 0;
|
||||
}
|
||||
if (!prefixcmp(var, "man."))
|
||||
return add_man_viewer_info(var, value);
|
||||
|
||||
return git_default_config(var, value, cb);
|
||||
}
|
||||
#include "help.h"
|
||||
|
||||
/* most GUI terminals set COLUMNS (although some don't export it) */
|
||||
static int term_columns(void)
|
||||
@@ -294,24 +25,9 @@ static int term_columns(void)
|
||||
return 80;
|
||||
}
|
||||
|
||||
static inline void mput_char(char c, unsigned int num)
|
||||
void add_cmdname(struct cmdnames *cmds, const char *name, int len)
|
||||
{
|
||||
while(num--)
|
||||
putchar(c);
|
||||
}
|
||||
|
||||
static struct cmdnames {
|
||||
int alloc;
|
||||
int cnt;
|
||||
struct cmdname {
|
||||
size_t len;
|
||||
char name[1];
|
||||
} **names;
|
||||
} main_cmds, other_cmds;
|
||||
|
||||
static void add_cmdname(struct cmdnames *cmds, const char *name, int len)
|
||||
{
|
||||
struct cmdname *ent = xmalloc(sizeof(*ent) + len);
|
||||
struct cmdname *ent = xmalloc(sizeof(*ent) + len + 1);
|
||||
|
||||
ent->len = len;
|
||||
memcpy(ent->name, name, len);
|
||||
@@ -342,7 +58,7 @@ static void uniq(struct cmdnames *cmds)
|
||||
cmds->cnt = j;
|
||||
}
|
||||
|
||||
static void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes)
|
||||
void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes)
|
||||
{
|
||||
int ci, cj, ei;
|
||||
int cmp;
|
||||
@@ -417,19 +133,21 @@ static int is_executable(const char *name)
|
||||
return st.st_mode & S_IXUSR;
|
||||
}
|
||||
|
||||
static unsigned int list_commands_in_dir(struct cmdnames *cmds,
|
||||
const char *path)
|
||||
static void list_commands_in_dir(struct cmdnames *cmds,
|
||||
const char *path,
|
||||
const char *prefix)
|
||||
{
|
||||
unsigned int longest = 0;
|
||||
const char *prefix = "git-";
|
||||
int prefix_len = strlen(prefix);
|
||||
int prefix_len;
|
||||
DIR *dir = opendir(path);
|
||||
struct dirent *de;
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
int len;
|
||||
|
||||
if (!dir)
|
||||
return 0;
|
||||
return;
|
||||
if (!prefix)
|
||||
prefix = "git-";
|
||||
prefix_len = strlen(prefix);
|
||||
|
||||
strbuf_addf(&buf, "%s/", path);
|
||||
len = buf.len;
|
||||
@@ -449,100 +167,81 @@ static unsigned int list_commands_in_dir(struct cmdnames *cmds,
|
||||
if (has_extension(de->d_name, ".exe"))
|
||||
entlen -= 4;
|
||||
|
||||
if (longest < entlen)
|
||||
longest = entlen;
|
||||
|
||||
add_cmdname(cmds, de->d_name + prefix_len, entlen);
|
||||
}
|
||||
closedir(dir);
|
||||
strbuf_release(&buf);
|
||||
|
||||
return longest;
|
||||
}
|
||||
|
||||
static unsigned int load_command_list(void)
|
||||
void load_command_list(const char *prefix,
|
||||
struct cmdnames *main_cmds,
|
||||
struct cmdnames *other_cmds)
|
||||
{
|
||||
unsigned int longest = 0;
|
||||
unsigned int len;
|
||||
const char *env_path = getenv("PATH");
|
||||
char *paths, *path, *colon;
|
||||
const char *exec_path = git_exec_path();
|
||||
|
||||
if (exec_path)
|
||||
longest = list_commands_in_dir(&main_cmds, exec_path);
|
||||
|
||||
if (!env_path) {
|
||||
fprintf(stderr, "PATH not set\n");
|
||||
exit(1);
|
||||
if (exec_path) {
|
||||
list_commands_in_dir(main_cmds, exec_path, prefix);
|
||||
qsort(main_cmds->names, main_cmds->cnt,
|
||||
sizeof(*main_cmds->names), cmdname_compare);
|
||||
uniq(main_cmds);
|
||||
}
|
||||
|
||||
path = paths = xstrdup(env_path);
|
||||
while (1) {
|
||||
if ((colon = strchr(path, PATH_SEP)))
|
||||
*colon = 0;
|
||||
if (env_path) {
|
||||
char *paths, *path, *colon;
|
||||
path = paths = xstrdup(env_path);
|
||||
while (1) {
|
||||
if ((colon = strchr(path, PATH_SEP)))
|
||||
*colon = 0;
|
||||
|
||||
len = list_commands_in_dir(&other_cmds, path);
|
||||
if (len > longest)
|
||||
longest = len;
|
||||
list_commands_in_dir(other_cmds, path, prefix);
|
||||
|
||||
if (!colon)
|
||||
break;
|
||||
path = colon + 1;
|
||||
if (!colon)
|
||||
break;
|
||||
path = colon + 1;
|
||||
}
|
||||
free(paths);
|
||||
|
||||
qsort(other_cmds->names, other_cmds->cnt,
|
||||
sizeof(*other_cmds->names), cmdname_compare);
|
||||
uniq(other_cmds);
|
||||
}
|
||||
free(paths);
|
||||
|
||||
qsort(main_cmds.names, main_cmds.cnt,
|
||||
sizeof(*main_cmds.names), cmdname_compare);
|
||||
uniq(&main_cmds);
|
||||
|
||||
qsort(other_cmds.names, other_cmds.cnt,
|
||||
sizeof(*other_cmds.names), cmdname_compare);
|
||||
uniq(&other_cmds);
|
||||
exclude_cmds(&other_cmds, &main_cmds);
|
||||
|
||||
return longest;
|
||||
exclude_cmds(other_cmds, main_cmds);
|
||||
}
|
||||
|
||||
static void list_commands(void)
|
||||
{
|
||||
unsigned int longest = load_command_list();
|
||||
const char *exec_path = git_exec_path();
|
||||
|
||||
if (main_cmds.cnt) {
|
||||
printf("available git commands in '%s'\n", exec_path);
|
||||
printf("----------------------------");
|
||||
mput_char('-', strlen(exec_path));
|
||||
putchar('\n');
|
||||
pretty_print_string_list(&main_cmds, longest);
|
||||
putchar('\n');
|
||||
}
|
||||
|
||||
if (other_cmds.cnt) {
|
||||
printf("git commands available from elsewhere on your $PATH\n");
|
||||
printf("---------------------------------------------------\n");
|
||||
pretty_print_string_list(&other_cmds, longest);
|
||||
putchar('\n');
|
||||
}
|
||||
}
|
||||
|
||||
void list_common_cmds_help(void)
|
||||
void list_commands(const char *title, struct cmdnames *main_cmds,
|
||||
struct cmdnames *other_cmds)
|
||||
{
|
||||
int i, longest = 0;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
|
||||
if (longest < strlen(common_cmds[i].name))
|
||||
longest = strlen(common_cmds[i].name);
|
||||
for (i = 0; i < main_cmds->cnt; i++)
|
||||
if (longest < main_cmds->names[i]->len)
|
||||
longest = main_cmds->names[i]->len;
|
||||
for (i = 0; i < other_cmds->cnt; i++)
|
||||
if (longest < other_cmds->names[i]->len)
|
||||
longest = other_cmds->names[i]->len;
|
||||
|
||||
if (main_cmds->cnt) {
|
||||
const char *exec_path = git_exec_path();
|
||||
printf("available %s in '%s'\n", title, exec_path);
|
||||
printf("----------------");
|
||||
mput_char('-', strlen(title) + strlen(exec_path));
|
||||
putchar('\n');
|
||||
pretty_print_string_list(main_cmds, longest);
|
||||
putchar('\n');
|
||||
}
|
||||
|
||||
puts("The most commonly used git commands are:");
|
||||
for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
|
||||
printf(" %s ", common_cmds[i].name);
|
||||
mput_char(' ', longest - strlen(common_cmds[i].name));
|
||||
puts(common_cmds[i].help);
|
||||
if (other_cmds->cnt) {
|
||||
printf("%s available from elsewhere on your $PATH\n", title);
|
||||
printf("---------------------------------------");
|
||||
mput_char('-', strlen(title));
|
||||
putchar('\n');
|
||||
pretty_print_string_list(other_cmds, longest);
|
||||
putchar('\n');
|
||||
}
|
||||
}
|
||||
|
||||
static int is_in_cmdlist(struct cmdnames *c, const char *s)
|
||||
int is_in_cmdlist(struct cmdnames *c, const char *s)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < c->cnt; i++)
|
||||
@@ -551,130 +250,6 @@ static int is_in_cmdlist(struct cmdnames *c, const char *s)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int is_git_command(const char *s)
|
||||
{
|
||||
load_command_list();
|
||||
return is_in_cmdlist(&main_cmds, s) ||
|
||||
is_in_cmdlist(&other_cmds, s) ||
|
||||
!strcmp(s, "help");
|
||||
}
|
||||
|
||||
static const char *prepend(const char *prefix, const char *cmd)
|
||||
{
|
||||
size_t pre_len = strlen(prefix);
|
||||
size_t cmd_len = strlen(cmd);
|
||||
char *p = xmalloc(pre_len + cmd_len + 1);
|
||||
memcpy(p, prefix, pre_len);
|
||||
strcpy(p + pre_len, cmd);
|
||||
return p;
|
||||
}
|
||||
|
||||
static const char *cmd_to_page(const char *git_cmd)
|
||||
{
|
||||
if (!git_cmd)
|
||||
return "git";
|
||||
else if (!prefixcmp(git_cmd, "git"))
|
||||
return git_cmd;
|
||||
else if (is_git_command(git_cmd))
|
||||
return prepend("git-", git_cmd);
|
||||
else
|
||||
return prepend("git", git_cmd);
|
||||
}
|
||||
|
||||
static void setup_man_path(void)
|
||||
{
|
||||
struct strbuf new_path;
|
||||
const char *old_path = getenv("MANPATH");
|
||||
|
||||
strbuf_init(&new_path, 0);
|
||||
|
||||
/* We should always put ':' after our path. If there is no
|
||||
* old_path, the ':' at the end will let 'man' to try
|
||||
* system-wide paths after ours to find the manual page. If
|
||||
* there is old_path, we need ':' as delimiter. */
|
||||
strbuf_addstr(&new_path, GIT_MAN_PATH);
|
||||
strbuf_addch(&new_path, ':');
|
||||
if (old_path)
|
||||
strbuf_addstr(&new_path, old_path);
|
||||
|
||||
setenv("MANPATH", new_path.buf, 1);
|
||||
|
||||
strbuf_release(&new_path);
|
||||
}
|
||||
|
||||
static void exec_viewer(const char *name, const char *page)
|
||||
{
|
||||
const char *info = get_man_viewer_info(name);
|
||||
|
||||
if (!strcasecmp(name, "man"))
|
||||
exec_man_man(info, page);
|
||||
else if (!strcasecmp(name, "woman"))
|
||||
exec_woman_emacs(info, page);
|
||||
else if (!strcasecmp(name, "konqueror"))
|
||||
exec_man_konqueror(info, page);
|
||||
else if (info)
|
||||
exec_man_cmd(info, page);
|
||||
else
|
||||
warning("'%s': unknown man viewer.", name);
|
||||
}
|
||||
|
||||
static void show_man_page(const char *git_cmd)
|
||||
{
|
||||
struct man_viewer_list *viewer;
|
||||
const char *page = cmd_to_page(git_cmd);
|
||||
|
||||
setup_man_path();
|
||||
for (viewer = man_viewer_list; viewer; viewer = viewer->next)
|
||||
{
|
||||
exec_viewer(viewer->name, page); /* will return when unable */
|
||||
}
|
||||
exec_viewer("man", page);
|
||||
die("no man viewer handled the request");
|
||||
}
|
||||
|
||||
static void show_info_page(const char *git_cmd)
|
||||
{
|
||||
const char *page = cmd_to_page(git_cmd);
|
||||
setenv("INFOPATH", GIT_INFO_PATH, 1);
|
||||
execlp("info", "info", "gitman", page, NULL);
|
||||
}
|
||||
|
||||
static void get_html_page_path(struct strbuf *page_path, const char *page)
|
||||
{
|
||||
struct stat st;
|
||||
const char *html_path = system_path(GIT_HTML_PATH);
|
||||
|
||||
/* Check that we have a git documentation directory. */
|
||||
if (stat(mkpath("%s/git.html", html_path), &st)
|
||||
|| !S_ISREG(st.st_mode))
|
||||
die("'%s': not a documentation directory.", html_path);
|
||||
|
||||
strbuf_init(page_path, 0);
|
||||
strbuf_addf(page_path, "%s/%s.html", html_path, page);
|
||||
}
|
||||
|
||||
/*
|
||||
* If open_html is not defined in a platform-specific way (see for
|
||||
* example compat/mingw.h), we use the script web--browse to display
|
||||
* HTML.
|
||||
*/
|
||||
#ifndef open_html
|
||||
void open_html(const char *path)
|
||||
{
|
||||
execl_git_cmd("web--browse", "-c", "help.browser", path, NULL);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void show_html_page(const char *git_cmd)
|
||||
{
|
||||
const char *page = cmd_to_page(git_cmd);
|
||||
struct strbuf page_path; /* it leaks but we exec bellow */
|
||||
|
||||
get_html_page_path(&page_path, page);
|
||||
|
||||
open_html(page_path.buf);
|
||||
}
|
||||
|
||||
void help_unknown_cmd(const char *cmd)
|
||||
{
|
||||
fprintf(stderr, "git: '%s' is not a git-command. See 'git --help'.\n", cmd);
|
||||
@@ -686,49 +261,3 @@ int cmd_version(int argc, const char **argv, const char *prefix)
|
||||
printf("git version %s\n", git_version_string);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cmd_help(int argc, const char **argv, const char *prefix)
|
||||
{
|
||||
int nongit;
|
||||
const char *alias;
|
||||
|
||||
setup_git_directory_gently(&nongit);
|
||||
git_config(git_help_config, NULL);
|
||||
|
||||
argc = parse_options(argc, argv, builtin_help_options,
|
||||
builtin_help_usage, 0);
|
||||
|
||||
if (show_all) {
|
||||
printf("usage: %s\n\n", git_usage_string);
|
||||
list_commands();
|
||||
printf("%s\n", git_more_info_string);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!argv[0]) {
|
||||
printf("usage: %s\n\n", git_usage_string);
|
||||
list_common_cmds_help();
|
||||
printf("\n%s\n", git_more_info_string);
|
||||
return 0;
|
||||
}
|
||||
|
||||
alias = alias_lookup(argv[0]);
|
||||
if (alias && !is_git_command(argv[0])) {
|
||||
printf("`git %s' is aliased to `%s'\n", argv[0], alias);
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (help_format) {
|
||||
case HELP_FORMAT_MAN:
|
||||
show_man_page(argv[0]);
|
||||
break;
|
||||
case HELP_FORMAT_INFO:
|
||||
show_info_page(argv[0]);
|
||||
break;
|
||||
case HELP_FORMAT_WEB:
|
||||
show_html_page(argv[0]);
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
29
help.h
Normal file
29
help.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#ifndef HELP_H
|
||||
#define HELP_H
|
||||
|
||||
struct cmdnames {
|
||||
int alloc;
|
||||
int cnt;
|
||||
struct cmdname {
|
||||
size_t len;
|
||||
char name[FLEX_ARRAY];
|
||||
} **names;
|
||||
};
|
||||
|
||||
static inline void mput_char(char c, unsigned int num)
|
||||
{
|
||||
while(num--)
|
||||
putchar(c);
|
||||
}
|
||||
|
||||
void load_command_list(const char *prefix,
|
||||
struct cmdnames *main_cmds,
|
||||
struct cmdnames *other_cmds);
|
||||
void add_cmdname(struct cmdnames *cmds, const char *name, int len);
|
||||
/* Here we require that excludes is a sorted list. */
|
||||
void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes);
|
||||
int is_in_cmdlist(struct cmdnames *c, const char *s);
|
||||
void list_commands(const char *title, struct cmdnames *main_cmds,
|
||||
struct cmdnames *other_cmds);
|
||||
|
||||
#endif /* HELP_H */
|
||||
44
index-pack.c
44
index-pack.c
@@ -654,7 +654,7 @@ static void parse_pack_objects(unsigned char *sha1)
|
||||
}
|
||||
}
|
||||
|
||||
static int write_compressed(int fd, void *in, unsigned int size, uint32_t *obj_crc)
|
||||
static int write_compressed(struct sha1file *f, void *in, unsigned int size)
|
||||
{
|
||||
z_stream stream;
|
||||
unsigned long maxsize;
|
||||
@@ -674,13 +674,12 @@ static int write_compressed(int fd, void *in, unsigned int size, uint32_t *obj_c
|
||||
deflateEnd(&stream);
|
||||
|
||||
size = stream.total_out;
|
||||
write_or_die(fd, out, size);
|
||||
*obj_crc = crc32(*obj_crc, out, size);
|
||||
sha1write(f, out, size);
|
||||
free(out);
|
||||
return size;
|
||||
}
|
||||
|
||||
static struct object_entry *append_obj_to_pack(
|
||||
static struct object_entry *append_obj_to_pack(struct sha1file *f,
|
||||
const unsigned char *sha1, void *buf,
|
||||
unsigned long size, enum object_type type)
|
||||
{
|
||||
@@ -696,15 +695,15 @@ static struct object_entry *append_obj_to_pack(
|
||||
s >>= 7;
|
||||
}
|
||||
header[n++] = c;
|
||||
write_or_die(output_fd, header, n);
|
||||
obj[0].idx.crc32 = crc32(0, Z_NULL, 0);
|
||||
obj[0].idx.crc32 = crc32(obj[0].idx.crc32, header, n);
|
||||
crc32_begin(f);
|
||||
sha1write(f, header, n);
|
||||
obj[0].size = size;
|
||||
obj[0].hdr_size = n;
|
||||
obj[0].type = type;
|
||||
obj[0].real_type = type;
|
||||
obj[1].idx.offset = obj[0].idx.offset + n;
|
||||
obj[1].idx.offset += write_compressed(output_fd, buf, size, &obj[0].idx.crc32);
|
||||
obj[1].idx.offset += write_compressed(f, buf, size);
|
||||
obj[0].idx.crc32 = crc32_end(f);
|
||||
hashcpy(obj->idx.sha1, sha1);
|
||||
return obj;
|
||||
}
|
||||
@@ -716,7 +715,7 @@ static int delta_pos_compare(const void *_a, const void *_b)
|
||||
return a->obj_no - b->obj_no;
|
||||
}
|
||||
|
||||
static void fix_unresolved_deltas(int nr_unresolved)
|
||||
static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved)
|
||||
{
|
||||
struct delta_entry **sorted_by_pos;
|
||||
int i, n = 0;
|
||||
@@ -754,8 +753,8 @@ static void fix_unresolved_deltas(int nr_unresolved)
|
||||
if (check_sha1_signature(d->base.sha1, base_obj.data,
|
||||
base_obj.size, typename(type)))
|
||||
die("local object %s is corrupt", sha1_to_hex(d->base.sha1));
|
||||
base_obj.obj = append_obj_to_pack(d->base.sha1, base_obj.data,
|
||||
base_obj.size, type);
|
||||
base_obj.obj = append_obj_to_pack(f, d->base.sha1,
|
||||
base_obj.data, base_obj.size, type);
|
||||
link_base_data(NULL, &base_obj);
|
||||
|
||||
find_delta_children(&d->base, &first, &last);
|
||||
@@ -875,7 +874,7 @@ int main(int argc, char **argv)
|
||||
const char *keep_name = NULL, *keep_msg = NULL;
|
||||
char *index_name_buf = NULL, *keep_name_buf = NULL;
|
||||
struct pack_idx_entry **idx_objects;
|
||||
unsigned char sha1[20];
|
||||
unsigned char pack_sha1[20];
|
||||
int nongit = 0;
|
||||
|
||||
setup_git_directory_gently(&nongit);
|
||||
@@ -962,13 +961,15 @@ int main(int argc, char **argv)
|
||||
parse_pack_header();
|
||||
objects = xmalloc((nr_objects + 1) * sizeof(struct object_entry));
|
||||
deltas = xmalloc(nr_objects * sizeof(struct delta_entry));
|
||||
parse_pack_objects(sha1);
|
||||
parse_pack_objects(pack_sha1);
|
||||
if (nr_deltas == nr_resolved_deltas) {
|
||||
stop_progress(&progress);
|
||||
/* Flush remaining pack final 20-byte SHA1. */
|
||||
flush();
|
||||
} else {
|
||||
if (fix_thin_pack) {
|
||||
struct sha1file *f;
|
||||
unsigned char read_sha1[20], tail_sha1[20];
|
||||
char msg[48];
|
||||
int nr_unresolved = nr_deltas - nr_resolved_deltas;
|
||||
int nr_objects_initial = nr_objects;
|
||||
@@ -977,12 +978,19 @@ int main(int argc, char **argv)
|
||||
objects = xrealloc(objects,
|
||||
(nr_objects + nr_unresolved + 1)
|
||||
* sizeof(*objects));
|
||||
fix_unresolved_deltas(nr_unresolved);
|
||||
f = sha1fd(output_fd, curr_pack);
|
||||
fix_unresolved_deltas(f, nr_unresolved);
|
||||
sprintf(msg, "completed with %d local objects",
|
||||
nr_objects - nr_objects_initial);
|
||||
stop_progress_msg(&progress, msg);
|
||||
fixup_pack_header_footer(output_fd, sha1,
|
||||
curr_pack, nr_objects);
|
||||
sha1close(f, tail_sha1, 0);
|
||||
hashcpy(read_sha1, pack_sha1);
|
||||
fixup_pack_header_footer(output_fd, pack_sha1,
|
||||
curr_pack, nr_objects,
|
||||
read_sha1, consumed_bytes-20);
|
||||
if (hashcmp(read_sha1, tail_sha1) != 0)
|
||||
die("Unexpected tail checksum for %s "
|
||||
"(disk corruption?)", curr_pack);
|
||||
}
|
||||
if (nr_deltas != nr_resolved_deltas)
|
||||
die("pack has %d unresolved deltas",
|
||||
@@ -995,13 +1003,13 @@ int main(int argc, char **argv)
|
||||
idx_objects = xmalloc((nr_objects) * sizeof(struct pack_idx_entry *));
|
||||
for (i = 0; i < nr_objects; i++)
|
||||
idx_objects[i] = &objects[i].idx;
|
||||
curr_index = write_idx_file(index_name, idx_objects, nr_objects, sha1);
|
||||
curr_index = write_idx_file(index_name, idx_objects, nr_objects, pack_sha1);
|
||||
free(idx_objects);
|
||||
|
||||
final(pack_name, curr_pack,
|
||||
index_name, curr_index,
|
||||
keep_name, keep_msg,
|
||||
sha1);
|
||||
pack_sha1);
|
||||
free(objects);
|
||||
free(index_name_buf);
|
||||
free(keep_name_buf);
|
||||
|
||||
@@ -27,7 +27,7 @@ static int merge_entry(int pos, const char *path)
|
||||
int found;
|
||||
|
||||
if (pos >= active_nr)
|
||||
die("git-merge-index: %s not in the cache", path);
|
||||
die("git merge-index: %s not in the cache", path);
|
||||
arguments[0] = pgm;
|
||||
arguments[1] = "";
|
||||
arguments[2] = "";
|
||||
@@ -53,7 +53,7 @@ static int merge_entry(int pos, const char *path)
|
||||
arguments[stage + 4] = ownbuf[stage];
|
||||
} while (++pos < active_nr);
|
||||
if (!found)
|
||||
die("git-merge-index: %s not in the cache", path);
|
||||
die("git merge-index: %s not in the cache", path);
|
||||
run_program();
|
||||
return found;
|
||||
}
|
||||
@@ -117,7 +117,7 @@ int main(int argc, char **argv)
|
||||
merge_all();
|
||||
continue;
|
||||
}
|
||||
die("git-merge-index: unknown option %s", arg);
|
||||
die("git merge-index: unknown option %s", arg);
|
||||
}
|
||||
merge_file(arg);
|
||||
}
|
||||
|
||||
@@ -142,3 +142,15 @@ struct revindex_entry *find_pack_revindex(struct packed_git *p, off_t ofs)
|
||||
} while (lo < hi);
|
||||
die("internal error: pack revindex corrupt");
|
||||
}
|
||||
|
||||
void discard_revindex(void)
|
||||
{
|
||||
if (pack_revindex_hashsz) {
|
||||
int i;
|
||||
for (i = 0; i < pack_revindex_hashsz; i++)
|
||||
if (pack_revindex[i].revindex)
|
||||
free(pack_revindex[i].revindex);
|
||||
free(pack_revindex);
|
||||
pack_revindex_hashsz = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,5 +7,6 @@ struct revindex_entry {
|
||||
};
|
||||
|
||||
struct revindex_entry *find_pack_revindex(struct packed_git *p, off_t ofs);
|
||||
void discard_revindex(void);
|
||||
|
||||
#endif
|
||||
|
||||
79
pack-write.c
79
pack-write.c
@@ -144,41 +144,94 @@ char *write_idx_file(char *index_name, struct pack_idx_entry **objects,
|
||||
return index_name;
|
||||
}
|
||||
|
||||
/*
|
||||
* Update pack header with object_count and compute new SHA1 for pack data
|
||||
* associated to pack_fd, and write that SHA1 at the end. That new SHA1
|
||||
* is also returned in new_pack_sha1.
|
||||
*
|
||||
* If partial_pack_sha1 is non null, then the SHA1 of the existing pack
|
||||
* (without the header update) is computed and validated against the
|
||||
* one provided in partial_pack_sha1. The validation is performed at
|
||||
* partial_pack_offset bytes in the pack file. The SHA1 of the remaining
|
||||
* data (i.e. from partial_pack_offset to the end) is then computed and
|
||||
* returned in partial_pack_sha1.
|
||||
*
|
||||
* Note that new_pack_sha1 is updated last, so both new_pack_sha1 and
|
||||
* partial_pack_sha1 can refer to the same buffer if the caller is not
|
||||
* interested in the resulting SHA1 of pack data above partial_pack_offset.
|
||||
*/
|
||||
void fixup_pack_header_footer(int pack_fd,
|
||||
unsigned char *pack_file_sha1,
|
||||
unsigned char *new_pack_sha1,
|
||||
const char *pack_name,
|
||||
uint32_t object_count)
|
||||
uint32_t object_count,
|
||||
unsigned char *partial_pack_sha1,
|
||||
off_t partial_pack_offset)
|
||||
{
|
||||
static const int buf_sz = 128 * 1024;
|
||||
SHA_CTX c;
|
||||
int aligned_sz, buf_sz = 8 * 1024;
|
||||
SHA_CTX old_sha1_ctx, new_sha1_ctx;
|
||||
struct pack_header hdr;
|
||||
char *buf;
|
||||
|
||||
SHA1_Init(&old_sha1_ctx);
|
||||
SHA1_Init(&new_sha1_ctx);
|
||||
|
||||
if (lseek(pack_fd, 0, SEEK_SET) != 0)
|
||||
die("Failed seeking to start: %s", strerror(errno));
|
||||
die("Failed seeking to start of %s: %s", pack_name, strerror(errno));
|
||||
if (read_in_full(pack_fd, &hdr, sizeof(hdr)) != sizeof(hdr))
|
||||
die("Unable to reread header of %s: %s", pack_name, strerror(errno));
|
||||
if (lseek(pack_fd, 0, SEEK_SET) != 0)
|
||||
die("Failed seeking to start: %s", strerror(errno));
|
||||
die("Failed seeking to start of %s: %s", pack_name, strerror(errno));
|
||||
SHA1_Update(&old_sha1_ctx, &hdr, sizeof(hdr));
|
||||
hdr.hdr_entries = htonl(object_count);
|
||||
SHA1_Update(&new_sha1_ctx, &hdr, sizeof(hdr));
|
||||
write_or_die(pack_fd, &hdr, sizeof(hdr));
|
||||
|
||||
SHA1_Init(&c);
|
||||
SHA1_Update(&c, &hdr, sizeof(hdr));
|
||||
partial_pack_offset -= sizeof(hdr);
|
||||
|
||||
buf = xmalloc(buf_sz);
|
||||
aligned_sz = buf_sz - sizeof(hdr);
|
||||
for (;;) {
|
||||
ssize_t n = xread(pack_fd, buf, buf_sz);
|
||||
ssize_t m, n;
|
||||
m = (partial_pack_sha1 && partial_pack_offset < aligned_sz) ?
|
||||
partial_pack_offset : aligned_sz;
|
||||
n = xread(pack_fd, buf, m);
|
||||
if (!n)
|
||||
break;
|
||||
if (n < 0)
|
||||
die("Failed to checksum %s: %s", pack_name, strerror(errno));
|
||||
SHA1_Update(&c, buf, n);
|
||||
SHA1_Update(&new_sha1_ctx, buf, n);
|
||||
|
||||
aligned_sz -= n;
|
||||
if (!aligned_sz)
|
||||
aligned_sz = buf_sz;
|
||||
|
||||
if (!partial_pack_sha1)
|
||||
continue;
|
||||
|
||||
SHA1_Update(&old_sha1_ctx, buf, n);
|
||||
partial_pack_offset -= n;
|
||||
if (partial_pack_offset == 0) {
|
||||
unsigned char sha1[20];
|
||||
SHA1_Final(sha1, &old_sha1_ctx);
|
||||
if (hashcmp(sha1, partial_pack_sha1) != 0)
|
||||
die("Unexpected checksum for %s "
|
||||
"(disk corruption?)", pack_name);
|
||||
/*
|
||||
* Now let's compute the SHA1 of the remainder of the
|
||||
* pack, which also means making partial_pack_offset
|
||||
* big enough not to matter anymore.
|
||||
*/
|
||||
SHA1_Init(&old_sha1_ctx);
|
||||
partial_pack_offset = ~partial_pack_offset;
|
||||
partial_pack_offset -= MSB(partial_pack_offset, 1);
|
||||
}
|
||||
}
|
||||
free(buf);
|
||||
|
||||
SHA1_Final(pack_file_sha1, &c);
|
||||
write_or_die(pack_fd, pack_file_sha1, 20);
|
||||
if (partial_pack_sha1)
|
||||
SHA1_Final(partial_pack_sha1, &old_sha1_ctx);
|
||||
SHA1_Final(new_pack_sha1, &new_sha1_ctx);
|
||||
write_or_die(pack_fd, new_pack_sha1, 20);
|
||||
fsync_or_die(pack_fd, pack_name);
|
||||
}
|
||||
|
||||
char *index_pack_lockfile(int ip_out)
|
||||
|
||||
2
pack.h
2
pack.h
@@ -58,7 +58,7 @@ struct pack_idx_entry {
|
||||
extern char *write_idx_file(char *index_name, struct pack_idx_entry **objects, int nr_objects, unsigned char *sha1);
|
||||
extern int check_pack_crc(struct packed_git *p, struct pack_window **w_curs, off_t offset, off_t len, unsigned int nr);
|
||||
extern int verify_pack(struct packed_git *);
|
||||
extern void fixup_pack_header_footer(int, unsigned char *, const char *, uint32_t);
|
||||
extern void fixup_pack_header_footer(int, unsigned char *, const char *, uint32_t, unsigned char *, off_t);
|
||||
extern char *index_pack_lockfile(int fd);
|
||||
|
||||
#define PH_ERROR_EOF (-1)
|
||||
|
||||
17
pretty.c
17
pretty.c
@@ -310,7 +310,7 @@ static int mailmap_name(struct strbuf *sb, const char *email)
|
||||
}
|
||||
|
||||
static size_t format_person_part(struct strbuf *sb, char part,
|
||||
const char *msg, int len)
|
||||
const char *msg, int len, enum date_mode dmode)
|
||||
{
|
||||
/* currently all placeholders have same length */
|
||||
const int placeholder_len = 2;
|
||||
@@ -377,7 +377,7 @@ static size_t format_person_part(struct strbuf *sb, char part,
|
||||
|
||||
switch (part) {
|
||||
case 'd': /* date */
|
||||
strbuf_addstr(sb, show_date(date, tz, DATE_NORMAL));
|
||||
strbuf_addstr(sb, show_date(date, tz, dmode));
|
||||
return placeholder_len;
|
||||
case 'D': /* date, RFC2822 style */
|
||||
strbuf_addstr(sb, show_date(date, tz, DATE_RFC2822));
|
||||
@@ -409,6 +409,7 @@ struct chunk {
|
||||
|
||||
struct format_commit_context {
|
||||
const struct commit *commit;
|
||||
enum date_mode dmode;
|
||||
|
||||
/* These offsets are relative to the start of the commit message. */
|
||||
int commit_header_parsed;
|
||||
@@ -584,10 +585,12 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder,
|
||||
return 1;
|
||||
case 'a': /* author ... */
|
||||
return format_person_part(sb, placeholder[1],
|
||||
msg + c->author.off, c->author.len);
|
||||
msg + c->author.off, c->author.len,
|
||||
c->dmode);
|
||||
case 'c': /* committer ... */
|
||||
return format_person_part(sb, placeholder[1],
|
||||
msg + c->committer.off, c->committer.len);
|
||||
msg + c->committer.off, c->committer.len,
|
||||
c->dmode);
|
||||
case 'e': /* encoding */
|
||||
strbuf_add(sb, msg + c->encoding.off, c->encoding.len);
|
||||
return 1;
|
||||
@@ -599,12 +602,14 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder,
|
||||
}
|
||||
|
||||
void format_commit_message(const struct commit *commit,
|
||||
const void *format, struct strbuf *sb)
|
||||
const void *format, struct strbuf *sb,
|
||||
enum date_mode dmode)
|
||||
{
|
||||
struct format_commit_context context;
|
||||
|
||||
memset(&context, 0, sizeof(context));
|
||||
context.commit = commit;
|
||||
context.dmode = dmode;
|
||||
strbuf_expand(sb, format, format_commit_item, &context);
|
||||
}
|
||||
|
||||
@@ -770,7 +775,7 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
|
||||
const char *encoding;
|
||||
|
||||
if (fmt == CMIT_FMT_USERFORMAT) {
|
||||
format_commit_message(commit, user_format, sb);
|
||||
format_commit_message(commit, user_format, sb, dmode);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
61
read-cache.c
61
read-cache.c
@@ -8,6 +8,11 @@
|
||||
#include "cache-tree.h"
|
||||
#include "refs.h"
|
||||
#include "dir.h"
|
||||
#include "tree.h"
|
||||
#include "commit.h"
|
||||
#include "diff.h"
|
||||
#include "diffcore.h"
|
||||
#include "revision.h"
|
||||
|
||||
/* Index extensions.
|
||||
*
|
||||
@@ -1483,3 +1488,59 @@ int read_index_unmerged(struct index_state *istate)
|
||||
istate->cache_nr = dst - istate->cache;
|
||||
return !!last;
|
||||
}
|
||||
|
||||
struct update_callback_data
|
||||
{
|
||||
int flags;
|
||||
int add_errors;
|
||||
};
|
||||
|
||||
static void update_callback(struct diff_queue_struct *q,
|
||||
struct diff_options *opt, void *cbdata)
|
||||
{
|
||||
int i;
|
||||
struct update_callback_data *data = cbdata;
|
||||
|
||||
for (i = 0; i < q->nr; i++) {
|
||||
struct diff_filepair *p = q->queue[i];
|
||||
const char *path = p->one->path;
|
||||
switch (p->status) {
|
||||
default:
|
||||
die("unexpected diff status %c", p->status);
|
||||
case DIFF_STATUS_UNMERGED:
|
||||
case DIFF_STATUS_MODIFIED:
|
||||
case DIFF_STATUS_TYPE_CHANGED:
|
||||
if (add_file_to_index(&the_index, path, data->flags)) {
|
||||
if (!(data->flags & ADD_CACHE_IGNORE_ERRORS))
|
||||
die("updating files failed");
|
||||
data->add_errors++;
|
||||
}
|
||||
break;
|
||||
case DIFF_STATUS_DELETED:
|
||||
if (data->flags & ADD_CACHE_IGNORE_REMOVAL)
|
||||
break;
|
||||
if (!(data->flags & ADD_CACHE_PRETEND))
|
||||
remove_file_from_index(&the_index, path);
|
||||
if (data->flags & (ADD_CACHE_PRETEND|ADD_CACHE_VERBOSE))
|
||||
printf("remove '%s'\n", path);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int add_files_to_cache(const char *prefix, const char **pathspec, int flags)
|
||||
{
|
||||
struct update_callback_data data;
|
||||
struct rev_info rev;
|
||||
init_revisions(&rev, prefix);
|
||||
setup_revisions(0, NULL, &rev, NULL);
|
||||
rev.prune_data = pathspec;
|
||||
rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
|
||||
rev.diffopt.format_callback = update_callback;
|
||||
data.flags = flags;
|
||||
data.add_errors = 0;
|
||||
rev.diffopt.format_callback_data = &data;
|
||||
run_diff_files(&rev, DIFF_RACY_IS_MODIFIED);
|
||||
return !!data.add_errors;
|
||||
}
|
||||
|
||||
|
||||
@@ -407,7 +407,7 @@ static const char *unpack(void)
|
||||
char keep_arg[256];
|
||||
struct child_process ip;
|
||||
|
||||
s = sprintf(keep_arg, "--keep=receive-pack %i on ", getpid());
|
||||
s = sprintf(keep_arg, "--keep=receive-pack %"PRIuMAX" on ", (uintmax_t) getpid());
|
||||
if (gethostname(keep_arg + s, sizeof(keep_arg) - s))
|
||||
strcpy(keep_arg + s, "localhost");
|
||||
|
||||
|
||||
223
revision.c
223
revision.c
@@ -489,7 +489,7 @@ static int add_parents_to_list(struct rev_info *revs, struct commit *commit,
|
||||
p->object.flags |= SEEN;
|
||||
insert_by_date_cached(p, list, cached_base, cache_ptr);
|
||||
}
|
||||
if(revs->first_parent_only)
|
||||
if (revs->first_parent_only)
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
@@ -953,22 +953,9 @@ static void add_grep(struct rev_info *revs, const char *ptn, enum grep_pat_token
|
||||
append_grep_pattern(&revs->grep_filter, ptn, "command line", 0, what);
|
||||
}
|
||||
|
||||
static void add_header_grep(struct rev_info *revs, const char *field, const char *pattern)
|
||||
static void add_header_grep(struct rev_info *revs, enum grep_header_field field, const char *pattern)
|
||||
{
|
||||
char *pat;
|
||||
const char *prefix;
|
||||
int patlen, fldlen;
|
||||
|
||||
fldlen = strlen(field);
|
||||
patlen = strlen(pattern);
|
||||
pat = xmalloc(patlen + fldlen + 10);
|
||||
prefix = ".*";
|
||||
if (*pattern == '^') {
|
||||
prefix = "";
|
||||
pattern++;
|
||||
}
|
||||
sprintf(pat, "^%s %s%s", field, prefix, pattern);
|
||||
add_grep(revs, pat, GREP_PATTERN_HEAD);
|
||||
append_header_grep_pattern(&revs->grep_filter, field, pattern);
|
||||
}
|
||||
|
||||
static void add_message_grep(struct rev_info *revs, const char *pattern)
|
||||
@@ -1041,6 +1028,11 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
|
||||
} else if (!strcmp(arg, "--topo-order")) {
|
||||
revs->lifo = 1;
|
||||
revs->topo_order = 1;
|
||||
} else if (!strcmp(arg, "--simplify-merges")) {
|
||||
revs->simplify_merges = 1;
|
||||
revs->rewrite_parents = 1;
|
||||
revs->simplify_history = 0;
|
||||
revs->limited = 1;
|
||||
} else if (!strcmp(arg, "--date-order")) {
|
||||
revs->lifo = 0;
|
||||
revs->topo_order = 1;
|
||||
@@ -1154,9 +1146,9 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
|
||||
* Grepping the commit log
|
||||
*/
|
||||
else if (!prefixcmp(arg, "--author=")) {
|
||||
add_header_grep(revs, "author", arg+9);
|
||||
add_header_grep(revs, GREP_HEADER_AUTHOR, arg+9);
|
||||
} else if (!prefixcmp(arg, "--committer=")) {
|
||||
add_header_grep(revs, "committer", arg+12);
|
||||
add_header_grep(revs, GREP_HEADER_COMMITTER, arg+12);
|
||||
} else if (!prefixcmp(arg, "--grep=")) {
|
||||
add_message_grep(revs, arg+7);
|
||||
} else if (!strcmp(arg, "--extended-regexp") || !strcmp(arg, "-E")) {
|
||||
@@ -1368,6 +1360,179 @@ static void add_child(struct rev_info *revs, struct commit *parent, struct commi
|
||||
l->next = add_decoration(&revs->children, &parent->object, l);
|
||||
}
|
||||
|
||||
static int remove_duplicate_parents(struct commit *commit)
|
||||
{
|
||||
struct commit_list **pp, *p;
|
||||
int surviving_parents;
|
||||
|
||||
/* Examine existing parents while marking ones we have seen... */
|
||||
pp = &commit->parents;
|
||||
while ((p = *pp) != NULL) {
|
||||
struct commit *parent = p->item;
|
||||
if (parent->object.flags & TMP_MARK) {
|
||||
*pp = p->next;
|
||||
continue;
|
||||
}
|
||||
parent->object.flags |= TMP_MARK;
|
||||
pp = &p->next;
|
||||
}
|
||||
/* count them while clearing the temporary mark */
|
||||
surviving_parents = 0;
|
||||
for (p = commit->parents; p; p = p->next) {
|
||||
p->item->object.flags &= ~TMP_MARK;
|
||||
surviving_parents++;
|
||||
}
|
||||
return surviving_parents;
|
||||
}
|
||||
|
||||
struct merge_simplify_state {
|
||||
struct commit *simplified;
|
||||
};
|
||||
|
||||
static struct merge_simplify_state *locate_simplify_state(struct rev_info *revs, struct commit *commit)
|
||||
{
|
||||
struct merge_simplify_state *st;
|
||||
|
||||
st = lookup_decoration(&revs->merge_simplification, &commit->object);
|
||||
if (!st) {
|
||||
st = xcalloc(1, sizeof(*st));
|
||||
add_decoration(&revs->merge_simplification, &commit->object, st);
|
||||
}
|
||||
return st;
|
||||
}
|
||||
|
||||
static struct commit_list **simplify_one(struct rev_info *revs, struct commit *commit, struct commit_list **tail)
|
||||
{
|
||||
struct commit_list *p;
|
||||
struct merge_simplify_state *st, *pst;
|
||||
int cnt;
|
||||
|
||||
st = locate_simplify_state(revs, commit);
|
||||
|
||||
/*
|
||||
* Have we handled this one?
|
||||
*/
|
||||
if (st->simplified)
|
||||
return tail;
|
||||
|
||||
/*
|
||||
* An UNINTERESTING commit simplifies to itself, so does a
|
||||
* root commit. We do not rewrite parents of such commit
|
||||
* anyway.
|
||||
*/
|
||||
if ((commit->object.flags & UNINTERESTING) || !commit->parents) {
|
||||
st->simplified = commit;
|
||||
return tail;
|
||||
}
|
||||
|
||||
/*
|
||||
* Do we know what commit all of our parents should be rewritten to?
|
||||
* Otherwise we are not ready to rewrite this one yet.
|
||||
*/
|
||||
for (cnt = 0, p = commit->parents; p; p = p->next) {
|
||||
pst = locate_simplify_state(revs, p->item);
|
||||
if (!pst->simplified) {
|
||||
tail = &commit_list_insert(p->item, tail)->next;
|
||||
cnt++;
|
||||
}
|
||||
}
|
||||
if (cnt) {
|
||||
tail = &commit_list_insert(commit, tail)->next;
|
||||
return tail;
|
||||
}
|
||||
|
||||
/*
|
||||
* Rewrite our list of parents.
|
||||
*/
|
||||
for (p = commit->parents; p; p = p->next) {
|
||||
pst = locate_simplify_state(revs, p->item);
|
||||
p->item = pst->simplified;
|
||||
}
|
||||
cnt = remove_duplicate_parents(commit);
|
||||
|
||||
/*
|
||||
* It is possible that we are a merge and one side branch
|
||||
* does not have any commit that touches the given paths;
|
||||
* in such a case, the immediate parents will be rewritten
|
||||
* to different commits.
|
||||
*
|
||||
* o----X X: the commit we are looking at;
|
||||
* / / o: a commit that touches the paths;
|
||||
* ---o----'
|
||||
*
|
||||
* Further reduce the parents by removing redundant parents.
|
||||
*/
|
||||
if (1 < cnt) {
|
||||
struct commit_list *h = reduce_heads(commit->parents);
|
||||
cnt = commit_list_count(h);
|
||||
free_commit_list(commit->parents);
|
||||
commit->parents = h;
|
||||
}
|
||||
|
||||
/*
|
||||
* A commit simplifies to itself if it is a root, if it is
|
||||
* UNINTERESTING, if it touches the given paths, or if it is a
|
||||
* merge and its parents simplifies to more than one commits
|
||||
* (the first two cases are already handled at the beginning of
|
||||
* this function).
|
||||
*
|
||||
* Otherwise, it simplifies to what its sole parent simplifies to.
|
||||
*/
|
||||
if (!cnt ||
|
||||
(commit->object.flags & UNINTERESTING) ||
|
||||
!(commit->object.flags & TREESAME) ||
|
||||
(1 < cnt))
|
||||
st->simplified = commit;
|
||||
else {
|
||||
pst = locate_simplify_state(revs, commit->parents->item);
|
||||
st->simplified = pst->simplified;
|
||||
}
|
||||
return tail;
|
||||
}
|
||||
|
||||
static void simplify_merges(struct rev_info *revs)
|
||||
{
|
||||
struct commit_list *list;
|
||||
struct commit_list *yet_to_do, **tail;
|
||||
|
||||
if (!revs->topo_order)
|
||||
sort_in_topological_order(&revs->commits, revs->lifo);
|
||||
if (!revs->prune)
|
||||
return;
|
||||
|
||||
/* feed the list reversed */
|
||||
yet_to_do = NULL;
|
||||
for (list = revs->commits; list; list = list->next)
|
||||
commit_list_insert(list->item, &yet_to_do);
|
||||
while (yet_to_do) {
|
||||
list = yet_to_do;
|
||||
yet_to_do = NULL;
|
||||
tail = &yet_to_do;
|
||||
while (list) {
|
||||
struct commit *commit = list->item;
|
||||
struct commit_list *next = list->next;
|
||||
free(list);
|
||||
list = next;
|
||||
tail = simplify_one(revs, commit, tail);
|
||||
}
|
||||
}
|
||||
|
||||
/* clean up the result, removing the simplified ones */
|
||||
list = revs->commits;
|
||||
revs->commits = NULL;
|
||||
tail = &revs->commits;
|
||||
while (list) {
|
||||
struct commit *commit = list->item;
|
||||
struct commit_list *next = list->next;
|
||||
struct merge_simplify_state *st;
|
||||
free(list);
|
||||
list = next;
|
||||
st = locate_simplify_state(revs, commit);
|
||||
if (st->simplified == commit)
|
||||
tail = &commit_list_insert(commit, tail)->next;
|
||||
}
|
||||
}
|
||||
|
||||
static void set_children(struct rev_info *revs)
|
||||
{
|
||||
struct commit_list *l;
|
||||
@@ -1408,6 +1573,8 @@ int prepare_revision_walk(struct rev_info *revs)
|
||||
return -1;
|
||||
if (revs->topo_order)
|
||||
sort_in_topological_order(&revs->commits, revs->lifo);
|
||||
if (revs->simplify_merges)
|
||||
simplify_merges(revs);
|
||||
if (revs->children.name)
|
||||
set_children(revs);
|
||||
return 0;
|
||||
@@ -1440,26 +1607,6 @@ static enum rewrite_result rewrite_one(struct rev_info *revs, struct commit **pp
|
||||
}
|
||||
}
|
||||
|
||||
static void remove_duplicate_parents(struct commit *commit)
|
||||
{
|
||||
struct commit_list **pp, *p;
|
||||
|
||||
/* Examine existing parents while marking ones we have seen... */
|
||||
pp = &commit->parents;
|
||||
while ((p = *pp) != NULL) {
|
||||
struct commit *parent = p->item;
|
||||
if (parent->object.flags & TMP_MARK) {
|
||||
*pp = p->next;
|
||||
continue;
|
||||
}
|
||||
parent->object.flags |= TMP_MARK;
|
||||
pp = &p->next;
|
||||
}
|
||||
/* ... and clear the temporary mark */
|
||||
for (p = commit->parents; p; p = p->next)
|
||||
p->item->object.flags &= ~TMP_MARK;
|
||||
}
|
||||
|
||||
static int rewrite_parents(struct rev_info *revs, struct commit *commit)
|
||||
{
|
||||
struct commit_list **pp = &commit->parents;
|
||||
|
||||
@@ -42,6 +42,7 @@ struct rev_info {
|
||||
simplify_history:1,
|
||||
lifo:1,
|
||||
topo_order:1,
|
||||
simplify_merges:1,
|
||||
tag_objects:1,
|
||||
tree_objects:1,
|
||||
blob_objects:1,
|
||||
@@ -110,6 +111,7 @@ struct rev_info {
|
||||
|
||||
struct reflog_walk_info *reflog_info;
|
||||
struct decoration children;
|
||||
struct decoration merge_simplification;
|
||||
};
|
||||
|
||||
#define REV_TREE_SAME 0
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user