mirror of
https://github.com/git/git.git
synced 2024-05-23 18:26:08 +02:00
Merge branch 'master' into js/fmt-patch
* master: (109 commits) t1300-repo-config: two new config parsing tests. Another config file parsing fix. update-index: plug memory leak from prefix_path() checkout-index: plug memory leak from prefix_path() update-index --unresolve: work from a subdirectory. pack-object: squelch eye-candy on non-tty core.prefersymlinkrefs: use symlinks for .git/HEAD repo-config: trim white-space before comment Fix for config file section parsing. Clarify git-cherry documentation. Update git-unpack-objects documentation. Fix up docs where "--" isn't displayed correctly. Several trivial documentation touch ups. git-svn 1.0.0 git-svn: documentation updates delta: stricter constness Makefile: do not link rev-list any specially. builtin-push: --all and --tags _are_ explicit refspecs builtin-log/whatchanged/show: make them official. show-branch: omit uninteresting merges. ...
This commit is contained in:
commit
c66b6c067e
|
@ -79,7 +79,7 @@ clean:
|
|||
asciidoc -b xhtml11 -d manpage -f asciidoc.conf $<
|
||||
|
||||
%.1 %.7 : %.xml
|
||||
xmlto man $<
|
||||
xmlto -m callouts.xsl man $<
|
||||
|
||||
%.xml : %.txt
|
||||
asciidoc -b docbook -d manpage -f asciidoc.conf $<
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
<!-- callout.xsl: converts asciidoc callouts to man page format -->
|
||||
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
|
||||
<xsl:template match="co">
|
||||
<xsl:value-of select="concat('\fB(',substring-after(@id,'-'),')\fR')"/>
|
||||
</xsl:template>
|
||||
<xsl:template match="calloutlist">
|
||||
<xsl:text>.sp </xsl:text>
|
||||
<xsl:apply-templates/>
|
||||
<xsl:text> </xsl:text>
|
||||
</xsl:template>
|
||||
<xsl:template match="callout">
|
||||
<xsl:value-of select="concat('\fB',substring-after(@arearefs,'-'),'. \fR')"/>
|
||||
<xsl:apply-templates/>
|
||||
<xsl:text>.br </xsl:text>
|
||||
</xsl:template>
|
||||
</xsl:stylesheet>
|
|
@ -0,0 +1,183 @@
|
|||
CONFIGURATION FILE
|
||||
------------------
|
||||
|
||||
The git configuration file contains a number of variables that affect
|
||||
the git commands behaviour. They can be used by both the git plumbing
|
||||
and the porcelains. The variables are divided to sections, where
|
||||
in the fully qualified variable name the variable itself is the last
|
||||
dot-separated segment and the section name is everything before the last
|
||||
dot. The variable names are case-insensitive and only alphanumeric
|
||||
characters are allowed. Some variables may appear multiple times.
|
||||
|
||||
The syntax is fairly flexible and permissive; whitespaces are mostly
|
||||
ignored. The '#' and ';' characters begin commends to the end of line,
|
||||
blank lines are ignored, lines containing strings enclosed in square
|
||||
brackets start sections and all the other lines are recognized
|
||||
as setting variables, in the form 'name = value'. If there is no equal
|
||||
sign on the line, the entire line is taken as 'name' and the variable
|
||||
is recognized as boolean "true". String values may be entirely or partially
|
||||
enclosed in double quotes; some variables may require special value format.
|
||||
|
||||
Example
|
||||
~~~~~~~
|
||||
|
||||
# Core variables
|
||||
[core]
|
||||
; Don't trust file modes
|
||||
filemode = false
|
||||
|
||||
# Our diff algorithm
|
||||
[diff]
|
||||
external = "/usr/local/bin/gnu-diff -u"
|
||||
renames = true
|
||||
|
||||
Variables
|
||||
~~~~~~~~~
|
||||
|
||||
Note that this list is non-comprehensive and not necessarily complete.
|
||||
For command-specific variables, you will find more detailed description
|
||||
in the appropriate manual page. You will find description of non-core
|
||||
porcelain configuration variables in the respective porcelain documentation.
|
||||
|
||||
core.fileMode::
|
||||
If false, the executable bit differences between the index and
|
||||
the working copy are ignored; useful on broken filesystems like FAT.
|
||||
See gitlink:git-update-index[1]. True by default.
|
||||
|
||||
core.gitProxy::
|
||||
A "proxy command" to execute (as 'command host port') instead
|
||||
of establishing direct connection to the remote server when
|
||||
using the git protocol for fetching. If the variable value is
|
||||
in the "COMMAND for DOMAIN" format, the command is applied only
|
||||
on hostnames ending with the specified domain string. This variable
|
||||
may be set multiple times and is matched in the given order;
|
||||
the first match wins.
|
||||
|
||||
Can be overriden by the 'GIT_PROXY_COMMAND' environment variable
|
||||
(which always applies universally, without the special "for"
|
||||
handling).
|
||||
|
||||
core.ignoreStat::
|
||||
The working copy files are assumed to stay unchanged until you
|
||||
mark them otherwise manually - Git will not detect the file changes
|
||||
by lstat() calls. This is useful on systems where those are very
|
||||
slow, such as Microsoft Windows. See gitlink:git-update-index[1].
|
||||
False by default.
|
||||
|
||||
core.preferSymlinkRefs::
|
||||
Instead of the default "symref" format for HEAD
|
||||
and other symbolic reference files, use symbolic links.
|
||||
This is sometimes needed to work with old scripts that
|
||||
expect HEAD to be a symbolic link.
|
||||
|
||||
core.repositoryFormatVersion::
|
||||
Internal variable identifying the repository format and layout
|
||||
version.
|
||||
|
||||
core.sharedRepository::
|
||||
If true, the repository is made shareable between several users
|
||||
in a group (making sure all the files and objects are group-writable).
|
||||
See gitlink:git-init-db[1]. False by default.
|
||||
|
||||
core.warnAmbiguousRefs::
|
||||
If true, git will warn you if the ref name you passed it is ambiguous
|
||||
and might match multiple refs in the .git/refs/ tree. True by default.
|
||||
|
||||
apply.whitespace::
|
||||
Tells `git-apply` how to handle whitespaces, in the same way
|
||||
as the '--whitespace' option. See gitlink:git-apply[1].
|
||||
|
||||
diff.renameLimit::
|
||||
The number of files to consider when performing the copy/rename
|
||||
detection; equivalent to the git diff option '-l'.
|
||||
|
||||
format.headers::
|
||||
Additional email headers to include in a patch to be submitted
|
||||
by mail. See gitlink:git-format-patch[1].
|
||||
|
||||
gitcvs.enabled::
|
||||
Whether the cvs pserver interface is enabled for this repository.
|
||||
See gitlink:git-cvsserver[1].
|
||||
|
||||
gitcvs.logfile::
|
||||
Path to a log file where the cvs pserver interface well... logs
|
||||
various stuff. See gitlink:git-cvsserver[1].
|
||||
|
||||
http.sslVerify::
|
||||
Whether to verify the SSL certificate when fetching or pushing
|
||||
over HTTPS. Can be overriden by the 'GIT_SSL_NO_VERIFY' environment
|
||||
variable.
|
||||
|
||||
http.sslCert::
|
||||
File containing the SSL certificate when fetching or pushing
|
||||
over HTTPS. Can be overriden by the 'GIT_SSL_CERT' environment
|
||||
variable.
|
||||
|
||||
http.sslKey::
|
||||
File containing the SSL private key when fetching or pushing
|
||||
over HTTPS. Can be overriden by the 'GIT_SSL_KEY' environment
|
||||
variable.
|
||||
|
||||
http.sslCAInfo::
|
||||
File containing the certificates to verify the peer with when
|
||||
fetching or pushing over HTTPS. Can be overriden by the
|
||||
'GIT_SSL_CAINFO' environment variable.
|
||||
|
||||
http.sslCAPath::
|
||||
Path containing files with the CA certificates to verify the peer
|
||||
with when fetching or pushing over HTTPS. Can be overriden
|
||||
by the 'GIT_SSL_CAPATH' environment variable.
|
||||
|
||||
http.maxRequests::
|
||||
How many HTTP requests to launch in parallel. Can be overriden
|
||||
by the 'GIT_HTTP_MAX_REQUESTS' environment variable. Default is 5.
|
||||
|
||||
http.lowSpeedLimit, http.lowSpeedTime::
|
||||
If the HTTP transfer speed is less than 'http.lowSpeedLimit'
|
||||
for longer than 'http.lowSpeedTime' seconds, the transfer is aborted.
|
||||
Can be overriden by the 'GIT_HTTP_LOW_SPEED_LIMIT' and
|
||||
'GIT_HTTP_LOW_SPEED_TIME' environment variables.
|
||||
|
||||
i18n.commitEncoding::
|
||||
Character encoding the commit messages are stored in; git itself
|
||||
does not care per se, but this information is necessary e.g. when
|
||||
importing commits from emails or in the gitk graphical history
|
||||
browser (and possibly at other places in the future or in other
|
||||
porcelains). See e.g. gitlink:git-mailinfo[1]. Defaults to 'utf-8'.
|
||||
|
||||
merge.summary::
|
||||
Whether to include summaries of merged commits in newly created
|
||||
merge commit messages. False by default.
|
||||
|
||||
pull.octopus::
|
||||
The default merge strategy to use when pulling multiple branches
|
||||
at once.
|
||||
|
||||
pull.twohead::
|
||||
The default merge strategy to use when pulling a single branch.
|
||||
|
||||
show.difftree::
|
||||
The default gitlink:git-diff-tree[1] arguments to be used
|
||||
for gitlink:git-show[1].
|
||||
|
||||
showbranch.default::
|
||||
The default set of branches for gitlink:git-show-branch[1].
|
||||
See gitlink:git-show-branch[1].
|
||||
|
||||
user.email::
|
||||
Your email address to be recorded in any newly created commits.
|
||||
Can be overriden by the 'GIT_AUTHOR_EMAIL' and 'GIT_COMMITTER_EMAIL'
|
||||
environment variables. See gitlink:git-commit-tree[1].
|
||||
|
||||
user.name::
|
||||
Your full name to be recorded in any newly created commits.
|
||||
Can be overriden by the 'GIT_AUTHOR_NAME' and 'GIT_COMMITTER_NAME'
|
||||
environment variables. See gitlink:git-commit-tree[1].
|
||||
|
||||
whatchanged.difftree::
|
||||
The default gitlink:git-diff-tree[1] arguments to be used
|
||||
for gitlink:git-whatchanged[1].
|
||||
|
||||
imap::
|
||||
The configuration variables in the 'imap' section are described
|
||||
in gitlink:git-imap-send[1].
|
|
@ -61,7 +61,8 @@ $ git prune
|
|||
$ git count-objects <2>
|
||||
$ git repack <3>
|
||||
$ git prune <4>
|
||||
|
||||
------------
|
||||
+
|
||||
<1> running without "--full" is usually cheap and assures the
|
||||
repository health reasonably well.
|
||||
<2> check how many loose objects there are and how much
|
||||
|
@ -69,17 +70,16 @@ diskspace is wasted by not repacking.
|
|||
<3> without "-a" repacks incrementally. repacking every 4-5MB
|
||||
of loose objects accumulation may be a good rule of thumb.
|
||||
<4> after repack, prune removes the duplicate loose objects.
|
||||
------------
|
||||
|
||||
Repack a small project into single pack.::
|
||||
+
|
||||
------------
|
||||
$ git repack -a -d <1>
|
||||
$ git prune
|
||||
|
||||
------------
|
||||
+
|
||||
<1> pack all the objects reachable from the refs into one pack
|
||||
and remove unneeded other packs
|
||||
------------
|
||||
|
||||
|
||||
Individual Developer (Standalone)[[Individual Developer (Standalone)]]
|
||||
|
@ -129,10 +129,10 @@ $ git-init-db
|
|||
$ git add . <1>
|
||||
$ git commit -m 'import of frotz source tree.'
|
||||
$ git tag v2.43 <2>
|
||||
|
||||
------------
|
||||
+
|
||||
<1> add everything under the current directory.
|
||||
<2> make a lightweight, unannotated tag.
|
||||
------------
|
||||
|
||||
Create a topic branch and develop.::
|
||||
+
|
||||
|
@ -153,7 +153,8 @@ $ git checkout master <9>
|
|||
$ git pull . alsa-audio <10>
|
||||
$ git log --since='3 days ago' <11>
|
||||
$ git log v2.43.. curses/ <12>
|
||||
|
||||
------------
|
||||
+
|
||||
<1> create a new topic branch.
|
||||
<2> revert your botched changes in "curses/ux_audio_oss.c".
|
||||
<3> you need to tell git if you added a new file; removal and
|
||||
|
@ -170,7 +171,6 @@ you originally wrote.
|
|||
combined and include --max-count=10 (show 10 commits), --until='2005-12-10'.
|
||||
<12> view only the changes that touch what's in curses/
|
||||
directory, since v2.43 tag.
|
||||
------------
|
||||
|
||||
|
||||
Individual Developer (Participant)[[Individual Developer (Participant)]]
|
||||
|
@ -208,7 +208,8 @@ $ git pull git://git.kernel.org/pub/.../jgarzik/libata-dev.git ALL <5>
|
|||
$ git reset --hard ORIG_HEAD <6>
|
||||
$ git prune <7>
|
||||
$ git fetch --tags <8>
|
||||
|
||||
------------
|
||||
+
|
||||
<1> repeat as needed.
|
||||
<2> extract patches from your branch for e-mail submission.
|
||||
<3> "pull" fetches from "origin" by default and merges into the
|
||||
|
@ -221,7 +222,6 @@ area we are interested in.
|
|||
<7> garbage collect leftover objects from reverted pull.
|
||||
<8> from time to time, obtain official tags from the "origin"
|
||||
and store them under .git/refs/tags/.
|
||||
------------
|
||||
|
||||
|
||||
Push into another repository.::
|
||||
|
@ -239,7 +239,8 @@ satellite$ git push origin <4>
|
|||
mothership$ cd frotz
|
||||
mothership$ git checkout master
|
||||
mothership$ git pull . satellite <5>
|
||||
|
||||
------------
|
||||
+
|
||||
<1> mothership machine has a frotz repository under your home
|
||||
directory; clone from it to start a repository on the satellite
|
||||
machine.
|
||||
|
@ -252,7 +253,6 @@ to local "origin" branch.
|
|||
mothership machine. You could use this as a back-up method.
|
||||
<5> on mothership machine, merge the work done on the satellite
|
||||
machine into the master branch.
|
||||
------------
|
||||
|
||||
Branch off of a specific tag.::
|
||||
+
|
||||
|
@ -262,12 +262,12 @@ $ edit/compile/test; git commit -a
|
|||
$ git checkout master
|
||||
$ git format-patch -k -m --stdout v2.6.14..private2.6.14 |
|
||||
git am -3 -k <2>
|
||||
|
||||
------------
|
||||
+
|
||||
<1> create a private branch based on a well known (but somewhat behind)
|
||||
tag.
|
||||
<2> forward port all changes in private2.6.14 branch to master branch
|
||||
without a formal "merging".
|
||||
------------
|
||||
|
||||
|
||||
Integrator[[Integrator]]
|
||||
|
@ -317,7 +317,8 @@ $ git tag -s -m 'GIT 0.99.9x' v0.99.9x <10>
|
|||
$ git fetch ko && git show-branch master maint 'tags/ko-*' <11>
|
||||
$ git push ko <12>
|
||||
$ git push ko v0.99.9x <13>
|
||||
|
||||
------------
|
||||
+
|
||||
<1> see what I was in the middle of doing, if any.
|
||||
<2> see what topic branches I have and think about how ready
|
||||
they are.
|
||||
|
@ -346,7 +347,6 @@ In the output from "git show-branch", "master" should have
|
|||
everything "ko-master" has.
|
||||
<12> push out the bleeding edge.
|
||||
<13> push the tag out, too.
|
||||
------------
|
||||
|
||||
|
||||
Repository Administration[[Repository Administration]]
|
||||
|
@ -367,7 +367,6 @@ example of managing a shared central repository.
|
|||
|
||||
Examples
|
||||
~~~~~~~~
|
||||
|
||||
Run git-daemon to serve /pub/scm from inetd.::
|
||||
+
|
||||
------------
|
||||
|
@ -388,13 +387,13 @@ cindy:x:1002:1002::/home/cindy:/usr/bin/git-shell
|
|||
david:x:1003:1003::/home/david:/usr/bin/git-shell
|
||||
$ grep git /etc/shells <2>
|
||||
/usr/bin/git-shell
|
||||
|
||||
------------
|
||||
+
|
||||
<1> log-in shell is set to /usr/bin/git-shell, which does not
|
||||
allow anything but "git push" and "git pull". The users should
|
||||
get an ssh access to the machine.
|
||||
<2> in many distributions /etc/shells needs to list what is used
|
||||
as the login shell.
|
||||
------------
|
||||
|
||||
CVS-style shared repository.::
|
||||
+
|
||||
|
@ -419,7 +418,8 @@ $ cat info/allowed-users <4>
|
|||
refs/heads/master alice\|cindy
|
||||
refs/heads/doc-update bob
|
||||
refs/tags/v[0-9]* david
|
||||
|
||||
------------
|
||||
+
|
||||
<1> place the developers into the same git group.
|
||||
<2> and make the shared repository writable by the group.
|
||||
<3> use update-hook example by Carl from Documentation/howto/
|
||||
|
@ -427,7 +427,6 @@ for branch policy control.
|
|||
<4> alice and cindy can push into master, only bob can push into doc-update.
|
||||
david is the release manager and is the only person who can
|
||||
create and push version tags.
|
||||
------------
|
||||
|
||||
HTTP server to support dumb protocol transfer.::
|
||||
+
|
||||
|
@ -435,7 +434,7 @@ HTTP server to support dumb protocol transfer.::
|
|||
dev$ git update-server-info <1>
|
||||
dev$ ftp user@isp.example.com <2>
|
||||
ftp> cp -r .git /home/user/myproject.git
|
||||
|
||||
------------
|
||||
+
|
||||
<1> make sure your info/refs and objects/info/packs are up-to-date
|
||||
<2> upload to public HTTP server hosted by your ISP.
|
||||
------------
|
||||
|
|
|
@ -26,7 +26,7 @@ OPTIONS
|
|||
-v::
|
||||
Be verbose.
|
||||
|
||||
--::
|
||||
\--::
|
||||
This option can be used to separate command-line options from
|
||||
the list of files, (useful when filenames might be mistaken
|
||||
for command-line options).
|
||||
|
|
|
@ -3,22 +3,27 @@ git-branch(1)
|
|||
|
||||
NAME
|
||||
----
|
||||
git-branch - Create a new branch, or remove an old one
|
||||
git-branch - List, create, or delete branches.
|
||||
|
||||
SYNOPSIS
|
||||
--------
|
||||
[verse]
|
||||
'git-branch' [[-f] <branchname> [<start-point>]]
|
||||
'git-branch' (-d | -D) <branchname>
|
||||
'git-branch' [-r]
|
||||
'git-branch' [-f] <branchname> [<start-point>]
|
||||
'git-branch' (-d | -D) <branchname>...
|
||||
|
||||
DESCRIPTION
|
||||
-----------
|
||||
If no argument is provided, show available branches and mark current
|
||||
branch with star. Otherwise, create a new branch of name <branchname>.
|
||||
If a starting point is also specified, that will be where the branch is
|
||||
created, otherwise it will be created at the current HEAD.
|
||||
With no arguments given (or just `-r`) a list of available branches
|
||||
will be shown, the current branch will be highlighted with an asterisk.
|
||||
|
||||
With a `-d` or `-D` option, `<branchname>` will be deleted.
|
||||
In its second form, a new branch named <branchname> will be created.
|
||||
It will start out with a head equal to the one given as <start-point>.
|
||||
If no <start-point> is given, the branch will be created with a head
|
||||
equal to that of the currently checked out branch.
|
||||
|
||||
With a `-d` or `-D` option, `<branchname>` will be deleted. You may
|
||||
specify more than one branch for deletion.
|
||||
|
||||
|
||||
OPTIONS
|
||||
|
@ -30,40 +35,56 @@ OPTIONS
|
|||
Delete a branch irrespective of its index status.
|
||||
|
||||
-f::
|
||||
Force a reset of <branchname> to <start-point> (or current head).
|
||||
Force the creation of a new branch even if it means deleting
|
||||
a branch that already exists with the same name.
|
||||
|
||||
-r::
|
||||
List only the "remote" branches.
|
||||
|
||||
<branchname>::
|
||||
The name of the branch to create or delete.
|
||||
|
||||
<start-point>::
|
||||
Where to create the branch; defaults to HEAD. This
|
||||
option has no meaning with -d and -D.
|
||||
The new branch will be created with a HEAD equal to this. It may
|
||||
be given as a branch name, a commit-id, or a tag. If this option
|
||||
is omitted, the current branch is assumed.
|
||||
|
||||
|
||||
|
||||
Examples
|
||||
~~~~~~~~
|
||||
--------
|
||||
|
||||
Start development off of a known tag::
|
||||
+
|
||||
------------
|
||||
$ git clone git://git.kernel.org/pub/scm/.../linux-2.6 my2.6
|
||||
$ cd my2.6
|
||||
$ git branch my2.6.14 v2.6.14 <1>
|
||||
$ git branch my2.6.14 v2.6.14 <1>
|
||||
$ git checkout my2.6.14
|
||||
|
||||
<1> These two steps are the same as "checkout -b my2.6.14 v2.6.14".
|
||||
------------
|
||||
+
|
||||
<1> This step and the next one could be combined into a single step with
|
||||
"checkout -b my2.6.14 v2.6.14".
|
||||
|
||||
Delete unneeded branch::
|
||||
+
|
||||
------------
|
||||
$ git clone git://git.kernel.org/.../git.git my.git
|
||||
$ cd my.git
|
||||
$ git branch -D todo <1>
|
||||
|
||||
$ git branch -D todo <1>
|
||||
------------
|
||||
+
|
||||
<1> delete todo branch even if the "master" branch does not have all
|
||||
commits from todo branch.
|
||||
------------
|
||||
|
||||
|
||||
Notes
|
||||
-----
|
||||
|
||||
If you are creating a branch that you want to immediately checkout, it's
|
||||
easier to use the git checkout command with its `-b` option to create
|
||||
a branch and check it out with a single command.
|
||||
|
||||
|
||||
Author
|
||||
------
|
||||
|
|
|
@ -63,7 +63,7 @@ OPTIONS
|
|||
Only meaningful with `--stdin`; paths are separated with
|
||||
NUL character instead of LF.
|
||||
|
||||
--::
|
||||
\--::
|
||||
Do not interpret any more arguments as options.
|
||||
|
||||
The order of the flags used to matter, but not anymore.
|
||||
|
|
|
@ -66,19 +66,19 @@ the `Makefile` to two revisions back, deletes hello.c by
|
|||
mistake, and gets it back from the index.
|
||||
+
|
||||
------------
|
||||
$ git checkout master <1>
|
||||
$ git checkout master~2 Makefile <2>
|
||||
$ git checkout master <1>
|
||||
$ git checkout master~2 Makefile <2>
|
||||
$ rm -f hello.c
|
||||
$ git checkout hello.c <3>
|
||||
|
||||
<1> switch branch
|
||||
<2> take out a file out of other commit
|
||||
<3> or "git checkout -- hello.c", as in the next example.
|
||||
$ git checkout hello.c <3>
|
||||
------------
|
||||
+
|
||||
If you have an unfortunate branch that is named `hello.c`, the
|
||||
last step above would be confused as an instruction to switch to
|
||||
that branch. You should instead write:
|
||||
<1> switch branch
|
||||
<2> take out a file out of other commit
|
||||
<3> restore hello.c from HEAD of current branch
|
||||
+
|
||||
If you have an unfortunate branch that is named `hello.c`, this
|
||||
step would be confused as an instruction to switch to that branch.
|
||||
You should instead write:
|
||||
+
|
||||
------------
|
||||
$ git checkout -- hello.c
|
||||
|
|
|
@ -11,11 +11,20 @@ SYNOPSIS
|
|||
|
||||
DESCRIPTION
|
||||
-----------
|
||||
Each commit between the fork-point and <head> is examined, and compared against
|
||||
the change each commit between the fork-point and <upstream> introduces.
|
||||
Commits already included in upstream are prefixed with '-' (meaning "drop from
|
||||
my local pull"), while commits missing from upstream are prefixed with '+'
|
||||
(meaning "add to the updated upstream").
|
||||
The changeset (or "diff") of each commit between the fork-point and <head>
|
||||
is compared against each commit between the fork-point and <upstream>.
|
||||
|
||||
Every commit with a changeset that doesn't exist in the other branch
|
||||
has its id (sha1) reported, prefixed by a symbol. Those existing only
|
||||
in the <upstream> branch are prefixed with a minus (-) sign, and those
|
||||
that only exist in the <head> branch are prefixed with a plus (+) symbol.
|
||||
|
||||
Because git-cherry compares the changeset rather than the commit id
|
||||
(sha1), you can use git-cherry to find out if a commit you made locally
|
||||
has been applied <upstream> under a different commit id. For example,
|
||||
this will happen if you're feeding patches <upstream> via email rather
|
||||
than pushing or pulling commits directly.
|
||||
|
||||
|
||||
OPTIONS
|
||||
-------
|
||||
|
|
|
@ -101,7 +101,7 @@ OPTIONS
|
|||
is not allowed.
|
||||
|
||||
Examples
|
||||
~~~~~~~~
|
||||
--------
|
||||
|
||||
Clone from upstream::
|
||||
+
|
||||
|
|
|
@ -106,7 +106,7 @@ but can be used to amend a merge commit.
|
|||
index and the latest commit does not match on the
|
||||
specified paths to avoid confusion.
|
||||
|
||||
--::
|
||||
\--::
|
||||
Do not interpret any more arguments as options.
|
||||
|
||||
<file>...::
|
||||
|
|
|
@ -7,13 +7,23 @@ git-count-objects - Reports on unpacked objects
|
|||
|
||||
SYNOPSIS
|
||||
--------
|
||||
'git-count-objects'
|
||||
'git-count-objects' [-v]
|
||||
|
||||
DESCRIPTION
|
||||
-----------
|
||||
This counts the number of unpacked object files and disk space consumed by
|
||||
them, to help you decide when it is a good time to repack.
|
||||
|
||||
|
||||
OPTIONS
|
||||
-------
|
||||
-v::
|
||||
In addition to the number of loose objects and disk
|
||||
space consumed, it reports the number of in-pack
|
||||
objects, and number of objects that can be removed by
|
||||
running `git-prune-packed`.
|
||||
|
||||
|
||||
Author
|
||||
------
|
||||
Written by Junio C Hamano <junkio@cox.net>
|
||||
|
|
|
@ -92,7 +92,7 @@ separated with a single space are given.
|
|||
Furthermore, it lists only files which were modified
|
||||
from all parents.
|
||||
|
||||
-cc::
|
||||
--cc::
|
||||
This flag changes the way a merge commit patch is displayed,
|
||||
in a similar way to the '-c' option. It implies the '-c'
|
||||
and '-p' options and further compresses the patch output
|
||||
|
|
|
@ -46,40 +46,41 @@ EXAMPLES
|
|||
Various ways to check your working tree::
|
||||
+
|
||||
------------
|
||||
$ git diff <1>
|
||||
$ git diff --cached <2>
|
||||
$ git diff HEAD <3>
|
||||
|
||||
$ git diff <1>
|
||||
$ git diff --cached <2>
|
||||
$ git diff HEAD <3>
|
||||
------------
|
||||
+
|
||||
<1> changes in the working tree since your last git-update-index.
|
||||
<2> changes between the index and your last commit; what you
|
||||
would be committing if you run "git commit" without "-a" option.
|
||||
<3> changes in the working tree since your last commit; what you
|
||||
would be committing if you run "git commit -a"
|
||||
------------
|
||||
|
||||
Comparing with arbitrary commits::
|
||||
+
|
||||
------------
|
||||
$ git diff test <1>
|
||||
$ git diff HEAD -- ./test <2>
|
||||
$ git diff HEAD^ HEAD <3>
|
||||
|
||||
$ git diff test <1>
|
||||
$ git diff HEAD -- ./test <2>
|
||||
$ git diff HEAD^ HEAD <3>
|
||||
------------
|
||||
+
|
||||
<1> instead of using the tip of the current branch, compare with the
|
||||
tip of "test" branch.
|
||||
<2> instead of comparing with the tip of "test" branch, compare with
|
||||
the tip of the current branch, but limit the comparison to the
|
||||
file "test".
|
||||
<3> compare the version before the last commit and the last commit.
|
||||
------------
|
||||
|
||||
|
||||
Limiting the diff output::
|
||||
+
|
||||
------------
|
||||
$ git diff --diff-filter=MRC <1>
|
||||
$ git diff --name-status -r <2>
|
||||
$ git diff arch/i386 include/asm-i386 <3>
|
||||
|
||||
$ git diff --diff-filter=MRC <1>
|
||||
$ git diff --name-status -r <2>
|
||||
$ git diff arch/i386 include/asm-i386 <3>
|
||||
------------
|
||||
+
|
||||
<1> show only modification, rename and copy, but not addition
|
||||
nor deletion.
|
||||
<2> show only names and the nature of change, but not actual
|
||||
|
@ -88,18 +89,17 @@ which in turn also disables recursive behaviour, so without -r
|
|||
you would only see the directory name if there is a change in a
|
||||
file in a subdirectory.
|
||||
<3> limit diff output to named subtrees.
|
||||
------------
|
||||
|
||||
Munging the diff output::
|
||||
+
|
||||
------------
|
||||
$ git diff --find-copies-harder -B -C <1>
|
||||
$ git diff -R <2>
|
||||
|
||||
$ git diff --find-copies-harder -B -C <1>
|
||||
$ git diff -R <2>
|
||||
------------
|
||||
+
|
||||
<1> spend extra cycles to find renames, copies and complete
|
||||
rewrites (very expensive).
|
||||
<2> output diff in reverse.
|
||||
------------
|
||||
|
||||
|
||||
Author
|
||||
|
|
|
@ -29,6 +29,7 @@ CONFIGURATION
|
|||
git-imap-send requires the following values in the repository
|
||||
configuration file (shown with examples):
|
||||
|
||||
..........................
|
||||
[imap]
|
||||
Folder = "INBOX.Drafts"
|
||||
|
||||
|
@ -38,8 +39,9 @@ configuration file (shown with examples):
|
|||
[imap]
|
||||
Host = imap.server.com
|
||||
User = bob
|
||||
Password = pwd
|
||||
Pass = pwd
|
||||
Port = 143
|
||||
..........................
|
||||
|
||||
|
||||
BUGS
|
||||
|
|
|
@ -60,12 +60,12 @@ Start a new git repository for an existing code base::
|
|||
+
|
||||
----------------
|
||||
$ cd /path/to/my/codebase
|
||||
$ git-init-db <1>
|
||||
$ git-add . <2>
|
||||
|
||||
$ git-init-db <1>
|
||||
$ git-add . <2>
|
||||
----------------
|
||||
+
|
||||
<1> prepare /path/to/my/codebase/.git directory
|
||||
<2> add all existing file to the index
|
||||
----------------
|
||||
|
||||
|
||||
Author
|
||||
|
|
|
@ -14,13 +14,12 @@ DESCRIPTION
|
|||
-----------
|
||||
Shows the commit logs.
|
||||
|
||||
The command takes options applicable to the gitlink::git-rev-list[1]
|
||||
The command takes options applicable to the gitlink:git-rev-list[1]
|
||||
command to control what is shown and how, and options applicable to
|
||||
the gitlink::git-diff-tree[1] commands to control how the change
|
||||
the gitlink:git-diff-tree[1] commands to control how the change
|
||||
each commit introduces are shown.
|
||||
|
||||
This manual page describes only the most frequently used
|
||||
options.
|
||||
This manual page describes only the most frequently used options.
|
||||
|
||||
|
||||
OPTIONS
|
||||
|
@ -52,7 +51,7 @@ git log v2.6.12.. include/scsi drivers/scsi::
|
|||
Show all commits since version 'v2.6.12' that changed any file
|
||||
in the include/scsi or drivers/scsi subdirectories
|
||||
|
||||
git log --since="2 weeks ago" -- gitk::
|
||||
git log --since="2 weeks ago" \-- gitk::
|
||||
|
||||
Show the changes during the last two weeks to the file 'gitk'.
|
||||
The "--" is necessary to avoid confusion with the *branch* named
|
||||
|
|
|
@ -106,7 +106,7 @@ OPTIONS
|
|||
lines, show only handful hexdigits prefix.
|
||||
Non default number of digits can be specified with --abbrev=<n>.
|
||||
|
||||
--::
|
||||
\--::
|
||||
Do not interpret any more arguments as options.
|
||||
|
||||
<file>::
|
||||
|
|
|
@ -8,7 +8,7 @@ git-merge-index - Runs a merge for files needing merging
|
|||
|
||||
SYNOPSIS
|
||||
--------
|
||||
'git-merge-index' [-o] [-q] <merge-program> (-a | -- | <file>\*)
|
||||
'git-merge-index' [-o] [-q] <merge-program> (-a | \-- | <file>\*)
|
||||
|
||||
DESCRIPTION
|
||||
-----------
|
||||
|
@ -19,7 +19,7 @@ files are passed as arguments 5, 6 and 7.
|
|||
|
||||
OPTIONS
|
||||
-------
|
||||
--::
|
||||
\--::
|
||||
Do not interpret any more arguments as options.
|
||||
|
||||
-a::
|
||||
|
|
|
@ -41,6 +41,7 @@ Enter git-name-rev:
|
|||
|
||||
------------
|
||||
% git name-rev 33db5f4d9027a10e477ccf054b2c1ab94f74c85a
|
||||
33db5f4d9027a10e477ccf054b2c1ab94f74c85a tags/v0.99^0~940
|
||||
------------
|
||||
|
||||
Now you are wiser, because you know that it happened 940 revisions before v0.99.
|
||||
|
|
|
@ -28,7 +28,7 @@ OPTIONS
|
|||
Do not remove anything; just report what it would
|
||||
remove.
|
||||
|
||||
--::
|
||||
\--::
|
||||
Do not interpret any more arguments as options.
|
||||
|
||||
<head>...::
|
||||
|
|
|
@ -3,38 +3,54 @@ git-rebase(1)
|
|||
|
||||
NAME
|
||||
----
|
||||
git-rebase - Rebase local commits to new upstream head
|
||||
git-rebase - Rebase local commits to a new head
|
||||
|
||||
SYNOPSIS
|
||||
--------
|
||||
'git-rebase' [--onto <newbase>] <upstream> [<branch>]
|
||||
|
||||
'git-rebase' --continue
|
||||
|
||||
'git-rebase' --abort
|
||||
|
||||
DESCRIPTION
|
||||
-----------
|
||||
git-rebase applies to <upstream> (or optionally to <newbase>) commits
|
||||
from <branch> that do not appear in <upstream>. When <branch> is not
|
||||
specified it defaults to the current branch (HEAD).
|
||||
git-rebase replaces <branch> with a new branch of the same name. When
|
||||
the --onto option is provided the new branch starts out with a HEAD equal
|
||||
to <newbase>, otherwise it is equal to <upstream>. It then attempts to
|
||||
create a new commit for each commit from the original <branch> that does
|
||||
not exist in the <upstream> branch.
|
||||
|
||||
When git-rebase is complete, <branch> will be updated to point to the
|
||||
newly created line of commit objects, so the previous line will not be
|
||||
accessible unless there are other references to it already.
|
||||
It is possible that a merge failure will prevent this process from being
|
||||
completely automatic. You will have to resolve any such merge failure
|
||||
and run `git rebase --continue`. If you can not resolve the merge
|
||||
failure, running `git rebase --abort` will restore the original <branch>
|
||||
and remove the working files found in the .dotest directory.
|
||||
|
||||
Note that if <branch> is not specified on the command line, the currently
|
||||
checked out branch is used.
|
||||
|
||||
Assume the following history exists and the current branch is "topic":
|
||||
|
||||
------------
|
||||
A---B---C topic
|
||||
/
|
||||
D---E---F---G master
|
||||
------------
|
||||
|
||||
From this point, the result of either of the following commands:
|
||||
|
||||
|
||||
git-rebase master
|
||||
git-rebase master topic
|
||||
|
||||
would be:
|
||||
|
||||
------------
|
||||
A'--B'--C' topic
|
||||
/
|
||||
D---E---F---G master
|
||||
------------
|
||||
|
||||
While, starting from the same point, the result of either of the following
|
||||
commands:
|
||||
|
@ -44,21 +60,33 @@ commands:
|
|||
|
||||
would be:
|
||||
|
||||
------------
|
||||
A'--B'--C' topic
|
||||
/
|
||||
D---E---F---G master
|
||||
------------
|
||||
|
||||
In case of conflict, git-rebase will stop at the first problematic commit
|
||||
and leave conflict markers in the tree. After resolving the conflict manually
|
||||
and updating the index with the desired resolution, you can continue the
|
||||
rebasing process with
|
||||
and leave conflict markers in the tree. You can use git diff to locate
|
||||
the markers (<<<<<<) and make edits to resolve the conflict. For each
|
||||
file you edit, you need to tell git that the conflict has been resolved,
|
||||
typically this would be done with
|
||||
|
||||
|
||||
git update-index <filename>
|
||||
|
||||
|
||||
After resolving the conflict manually and updating the index with the
|
||||
desired resolution, you can continue the rebasing process with
|
||||
|
||||
|
||||
git rebase --continue
|
||||
|
||||
git am --resolved --3way
|
||||
|
||||
Alternatively, you can undo the git-rebase with
|
||||
|
||||
git reset --hard ORIG_HEAD
|
||||
rm -r .dotest
|
||||
|
||||
git rebase --abort
|
||||
|
||||
OPTIONS
|
||||
-------
|
||||
|
@ -73,6 +101,28 @@ OPTIONS
|
|||
<branch>::
|
||||
Working branch; defaults to HEAD.
|
||||
|
||||
--continue::
|
||||
Restart the rebasing process after having resolved a merge conflict.
|
||||
|
||||
--abort::
|
||||
Restore the original branch and abort the rebase operation.
|
||||
|
||||
NOTES
|
||||
-----
|
||||
When you rebase a branch, you are changing its history in a way that
|
||||
will cause problems for anyone who already has a copy of the branch
|
||||
in their repository and tries to pull updates from you. You should
|
||||
understand the implications of using 'git rebase' on a repository that
|
||||
you share.
|
||||
|
||||
When the git rebase command is run, it will first execute a "pre-rebase"
|
||||
hook if one exists. You can use this hook to do sanity checks and
|
||||
reject the rebase if it isn't appropriate. Please see the template
|
||||
pre-rebase hook script for an example.
|
||||
|
||||
You must be in the top directory of your project to start (or continue)
|
||||
a rebase. Upon completion, <branch> will be the current branch.
|
||||
|
||||
Author
|
||||
------
|
||||
Written by Junio C Hamano <junkio@cox.net>
|
||||
|
|
|
@ -38,6 +38,7 @@ OPTIONS
|
|||
-d::
|
||||
After packing, if the newly created packs make some
|
||||
existing packs redundant, remove the redundant packs.
|
||||
Also runs gitlink:git-prune-packed[1].
|
||||
|
||||
-l::
|
||||
Pass the `--local` option to `git pack-objects`, see
|
||||
|
|
|
@ -15,6 +15,7 @@ SYNOPSIS
|
|||
'git-repo-config' [type] --get-all name [value_regex]
|
||||
'git-repo-config' [type] --unset name [value_regex]
|
||||
'git-repo-config' [type] --unset-all name [value_regex]
|
||||
'git-repo-config' -l | --list
|
||||
|
||||
DESCRIPTION
|
||||
-----------
|
||||
|
@ -33,10 +34,10 @@ convert the value to the canonical form (simple decimal number for int,
|
|||
a "true" or "false" string for bool). If no type specifier is passed,
|
||||
no checks or transformations are performed on the value.
|
||||
|
||||
This command will fail if
|
||||
This command will fail if:
|
||||
|
||||
. .git/config is invalid,
|
||||
. .git/config can not be written to,
|
||||
. The .git/config file is invalid,
|
||||
. Can not write to .git/config,
|
||||
. no section was provided,
|
||||
. the section or key is invalid,
|
||||
. you try to unset an option which does not exist, or
|
||||
|
@ -48,7 +49,7 @@ OPTIONS
|
|||
|
||||
--replace-all::
|
||||
Default behaviour is to replace at most one line. This replaces
|
||||
all lines matching the key (and optionally the value_regex)
|
||||
all lines matching the key (and optionally the value_regex).
|
||||
|
||||
--get::
|
||||
Get the value for a given key (optionally filtered by a regex
|
||||
|
@ -58,12 +59,18 @@ OPTIONS
|
|||
Like get, but does not fail if the number of values for the key
|
||||
is not exactly one.
|
||||
|
||||
--get-regexp::
|
||||
Like --get-all, but interprets the name as a regular expression.
|
||||
|
||||
--unset::
|
||||
Remove the line matching the key from .git/config.
|
||||
|
||||
--unset-all::
|
||||
Remove all matching lines from .git/config.
|
||||
|
||||
-l, --list::
|
||||
List all variables set in .git/config.
|
||||
|
||||
|
||||
EXAMPLE
|
||||
-------
|
||||
|
@ -87,11 +94,11 @@ Given a .git/config like this:
|
|||
renames = true
|
||||
|
||||
; Proxy settings
|
||||
[proxy]
|
||||
command="ssh" for "ssh://kernel.org/"
|
||||
command="proxy-command" for kernel.org
|
||||
command="myprotocol-command" for "my://"
|
||||
command=default-proxy ; for all the rest
|
||||
[core]
|
||||
gitproxy="ssh" for "ssh://kernel.org/"
|
||||
gitproxy="proxy-command" for kernel.org
|
||||
gitproxy="myprotocol-command" for "my://"
|
||||
gitproxy=default-proxy ; for all the rest
|
||||
|
||||
you can set the filemode to true with
|
||||
|
||||
|
@ -104,7 +111,7 @@ to what URL they apply. Here is how to change the entry for kernel.org
|
|||
to "ssh".
|
||||
|
||||
------------
|
||||
% git repo-config proxy.command '"ssh" for kernel.org' 'for kernel.org$'
|
||||
% git repo-config core.gitproxy '"ssh" for kernel.org' 'for kernel.org$'
|
||||
------------
|
||||
|
||||
This makes sure that only the key/value pair for kernel.org is replaced.
|
||||
|
@ -115,7 +122,7 @@ To delete the entry for renames, do
|
|||
% git repo-config --unset diff.renames
|
||||
------------
|
||||
|
||||
If you want to delete an entry for a multivar (like proxy.command above),
|
||||
If you want to delete an entry for a multivar (like core.gitproxy above),
|
||||
you have to provide a regex matching the value of exactly one line.
|
||||
|
||||
To query the value for a given key, do
|
||||
|
@ -133,27 +140,27 @@ or
|
|||
or, to query a multivar:
|
||||
|
||||
------------
|
||||
% git repo-config --get proxy.command "for kernel.org$"
|
||||
% git repo-config --get core.gitproxy "for kernel.org$"
|
||||
------------
|
||||
|
||||
If you want to know all the values for a multivar, do:
|
||||
|
||||
------------
|
||||
% git repo-config --get-all proxy.command
|
||||
% git repo-config --get-all core.gitproxy
|
||||
------------
|
||||
|
||||
If you like to live dangerous, you can replace *all* proxy.commands by a
|
||||
If you like to live dangerous, you can replace *all* core.gitproxy by a
|
||||
new one with
|
||||
|
||||
------------
|
||||
% git repo-config --replace-all proxy.command ssh
|
||||
% git repo-config --replace-all core.gitproxy ssh
|
||||
------------
|
||||
|
||||
However, if you really only want to replace the line for the default proxy,
|
||||
i.e. the one without a "for ..." postfix, do something like this:
|
||||
|
||||
------------
|
||||
% git repo-config proxy.command ssh '! for '
|
||||
% git repo-config core.gitproxy ssh '! for '
|
||||
------------
|
||||
|
||||
To actually match only values with an exclamation mark, you have to
|
||||
|
@ -163,13 +170,16 @@ To actually match only values with an exclamation mark, you have to
|
|||
------------
|
||||
|
||||
|
||||
include::config.txt[]
|
||||
|
||||
|
||||
Author
|
||||
------
|
||||
Written by Johannes Schindelin <Johannes.Schindelin@gmx.de>
|
||||
|
||||
Documentation
|
||||
--------------
|
||||
Documentation by Johannes Schindelin.
|
||||
Documentation by Johannes Schindelin, Petr Baudis and the git-list <git@vger.kernel.org>.
|
||||
|
||||
GIT
|
||||
---
|
||||
|
|
|
@ -43,16 +43,17 @@ OPTIONS
|
|||
Commit to make the current HEAD.
|
||||
|
||||
Examples
|
||||
~~~~~~~~
|
||||
--------
|
||||
|
||||
Undo a commit and redo::
|
||||
+
|
||||
------------
|
||||
$ git commit ...
|
||||
$ git reset --soft HEAD^ <1>
|
||||
$ edit <2>
|
||||
$ git commit -a -c ORIG_HEAD <3>
|
||||
|
||||
$ git reset --soft HEAD^ <1>
|
||||
$ edit <2>
|
||||
$ git commit -a -c ORIG_HEAD <3>
|
||||
------------
|
||||
+
|
||||
<1> This is most often done when you remembered what you
|
||||
just committed is incomplete, or you misspelled your commit
|
||||
message, or both. Leaves working tree as it was before "reset".
|
||||
|
@ -60,43 +61,43 @@ message, or both. Leaves working tree as it was before "reset".
|
|||
<3> "reset" copies the old head to .git/ORIG_HEAD; redo the
|
||||
commit by starting with its log message. If you do not need to
|
||||
edit the message further, you can give -C option instead.
|
||||
------------
|
||||
|
||||
Undo commits permanently::
|
||||
+
|
||||
------------
|
||||
$ git commit ...
|
||||
$ git reset --hard HEAD~3 <1>
|
||||
|
||||
$ git reset --hard HEAD~3 <1>
|
||||
------------
|
||||
+
|
||||
<1> The last three commits (HEAD, HEAD^, and HEAD~2) were bad
|
||||
and you do not want to ever see them again. Do *not* do this if
|
||||
you have already given these commits to somebody else.
|
||||
------------
|
||||
|
||||
Undo a commit, making it a topic branch::
|
||||
+
|
||||
------------
|
||||
$ git branch topic/wip <1>
|
||||
$ git reset --hard HEAD~3 <2>
|
||||
$ git checkout topic/wip <3>
|
||||
|
||||
$ git branch topic/wip <1>
|
||||
$ git reset --hard HEAD~3 <2>
|
||||
$ git checkout topic/wip <3>
|
||||
------------
|
||||
+
|
||||
<1> You have made some commits, but realize they were premature
|
||||
to be in the "master" branch. You want to continue polishing
|
||||
them in a topic branch, so create "topic/wip" branch off of the
|
||||
current HEAD.
|
||||
<2> Rewind the master branch to get rid of those three commits.
|
||||
<3> Switch to "topic/wip" branch and keep working.
|
||||
------------
|
||||
|
||||
Undo update-index::
|
||||
+
|
||||
------------
|
||||
$ edit <1>
|
||||
$ edit <1>
|
||||
$ git-update-index frotz.c filfre.c
|
||||
$ mailx <2>
|
||||
$ git reset <3>
|
||||
$ git pull git://info.example.com/ nitfol <4>
|
||||
|
||||
$ mailx <2>
|
||||
$ git reset <3>
|
||||
$ git pull git://info.example.com/ nitfol <4>
|
||||
------------
|
||||
+
|
||||
<1> you are happily working on something, and find the changes
|
||||
in these files are in good order. You do not want to see them
|
||||
when you run "git diff", because you plan to work on other files
|
||||
|
@ -109,12 +110,11 @@ index changes for these two files. Your changes in working tree
|
|||
remain there.
|
||||
<4> then you can pull and merge, leaving frotz.c and filfre.c
|
||||
changes still in the working tree.
|
||||
------------
|
||||
|
||||
Undo a merge or pull::
|
||||
+
|
||||
------------
|
||||
$ git pull <1>
|
||||
$ git pull <1>
|
||||
Trying really trivial in-index merge...
|
||||
fatal: Merge requires file-level merging
|
||||
Nope.
|
||||
|
@ -122,20 +122,19 @@ Nope.
|
|||
Auto-merging nitfol
|
||||
CONFLICT (content): Merge conflict in nitfol
|
||||
Automatic merge failed/prevented; fix up by hand
|
||||
$ git reset --hard <2>
|
||||
|
||||
$ git reset --hard <2>
|
||||
$ git pull . topic/branch <3>
|
||||
Updating from 41223... to 13134...
|
||||
Fast forward
|
||||
$ git reset --hard ORIG_HEAD <4>
|
||||
------------
|
||||
+
|
||||
<1> try to update from the upstream resulted in a lot of
|
||||
conflicts; you were not ready to spend a lot of time merging
|
||||
right now, so you decide to do that later.
|
||||
<2> "pull" has not made merge commit, so "git reset --hard"
|
||||
which is a synonym for "git reset --hard HEAD" clears the mess
|
||||
from the index file and the working tree.
|
||||
|
||||
$ git pull . topic/branch <3>
|
||||
Updating from 41223... to 13134...
|
||||
Fast forward
|
||||
$ git reset --hard ORIG_HEAD <4>
|
||||
|
||||
<3> merge a topic branch into the current branch, which resulted
|
||||
in a fast forward.
|
||||
<4> but you decided that the topic branch is not ready for public
|
||||
|
@ -143,7 +142,6 @@ consumption yet. "pull" or "merge" always leaves the original
|
|||
tip of the current branch in ORIG_HEAD, so resetting hard to it
|
||||
brings your index file and the working tree back to that state,
|
||||
and resets the tip of the branch to that commit.
|
||||
------------
|
||||
|
||||
Interrupted workflow::
|
||||
+
|
||||
|
@ -155,21 +153,21 @@ need to get to the other branch for a quick bugfix.
|
|||
------------
|
||||
$ git checkout feature ;# you were working in "feature" branch and
|
||||
$ work work work ;# got interrupted
|
||||
$ git commit -a -m 'snapshot WIP' <1>
|
||||
$ git commit -a -m 'snapshot WIP' <1>
|
||||
$ git checkout master
|
||||
$ fix fix fix
|
||||
$ git commit ;# commit with real log
|
||||
$ git checkout feature
|
||||
$ git reset --soft HEAD^ ;# go back to WIP state <2>
|
||||
$ git reset <3>
|
||||
|
||||
$ git reset --soft HEAD^ ;# go back to WIP state <2>
|
||||
$ git reset <3>
|
||||
------------
|
||||
+
|
||||
<1> This commit will get blown away so a throw-away log message is OK.
|
||||
<2> This removes the 'WIP' commit from the commit history, and sets
|
||||
your working tree to the state just before you made that snapshot.
|
||||
<3> After <2>, the index file still has all the WIP changes you
|
||||
committed in <1>. This sets it to the last commit you were
|
||||
basing the WIP changes on.
|
||||
------------
|
||||
<3> At this point the index file still has all the WIP changes you
|
||||
committed as 'snapshot WIP'. This updates the index to show your
|
||||
WIP files as uncommitted.
|
||||
|
||||
Author
|
||||
------
|
||||
|
|
|
@ -32,7 +32,7 @@ OPTIONS
|
|||
-v::
|
||||
Be verbose.
|
||||
|
||||
--::
|
||||
\--::
|
||||
This option can be used to separate command-line options from
|
||||
the list of files, (useful when filenames might be mistaken
|
||||
for command-line options).
|
||||
|
|
|
@ -13,9 +13,16 @@ SYNOPSIS
|
|||
|
||||
DESCRIPTION
|
||||
-----------
|
||||
Reads a packed archive (.pack) from the standard input, and
|
||||
expands the objects contained in the pack into "one-file
|
||||
one-object" format in $GIT_OBJECT_DIRECTORY.
|
||||
Read a packed archive (.pack) from the standard input, expanding
|
||||
the objects contained within and writing them into the repository in
|
||||
"loose" (one object per file) format.
|
||||
|
||||
Objects that already exist in the repository will *not* be unpacked
|
||||
from the pack-file. Therefore, nothing will be unpacked if you use
|
||||
this command on a pack-file that exists within the target repository.
|
||||
|
||||
Please see the `git-repack` documentation for options to generate
|
||||
new packs and replace existing ones.
|
||||
|
||||
OPTIONS
|
||||
-------
|
||||
|
|
|
@ -10,12 +10,12 @@ SYNOPSIS
|
|||
--------
|
||||
[verse]
|
||||
'git-update-index'
|
||||
[--add] [--remove | --force-remove] [--replace]
|
||||
[--refresh [-q] [--unmerged] [--ignore-missing]]
|
||||
[--add] [--remove | --force-remove] [--replace]
|
||||
[--refresh] [-q] [--unmerged] [--ignore-missing]
|
||||
[--cacheinfo <mode> <object> <file>]\*
|
||||
[--chmod=(+|-)x]
|
||||
[--assume-unchanged | --no-assume-unchanged]
|
||||
[--really-refresh]
|
||||
[--really-refresh] [--unresolve]
|
||||
[--info-only] [--index-info]
|
||||
[-z] [--stdin]
|
||||
[--verbose]
|
||||
|
@ -80,6 +80,10 @@ OPTIONS
|
|||
filesystem that has very slow lstat(2) system call
|
||||
(e.g. cifs).
|
||||
|
||||
--unresolve::
|
||||
Restores the 'unmerged' or 'needs updating' state of a
|
||||
file during a merge if it was cleared by accident.
|
||||
|
||||
--info-only::
|
||||
Do not create objects in the object database for all
|
||||
<file> arguments that follow this flag; just insert
|
||||
|
@ -109,7 +113,7 @@ OPTIONS
|
|||
Only meaningful with `--stdin`; paths are separated with
|
||||
NUL character instead of LF.
|
||||
|
||||
--::
|
||||
\--::
|
||||
Do not interpret any more arguments as options.
|
||||
|
||||
<file>::
|
||||
|
@ -247,34 +251,33 @@ To update and refresh only the files already checked out:
|
|||
$ git-checkout-index -n -f -a && git-update-index --ignore-missing --refresh
|
||||
----------------
|
||||
|
||||
On an inefficient filesystem with `core.ignorestat` set:
|
||||
|
||||
On an inefficient filesystem with `core.ignorestat` set::
|
||||
+
|
||||
------------
|
||||
$ git update-index --really-refresh <1>
|
||||
$ git update-index --no-assume-unchanged foo.c <2>
|
||||
$ git diff --name-only <3>
|
||||
$ git update-index --really-refresh <1>
|
||||
$ git update-index --no-assume-unchanged foo.c <2>
|
||||
$ git diff --name-only <3>
|
||||
$ edit foo.c
|
||||
$ git diff --name-only <4>
|
||||
$ git diff --name-only <4>
|
||||
M foo.c
|
||||
$ git update-index foo.c <5>
|
||||
$ git diff --name-only <6>
|
||||
$ git update-index foo.c <5>
|
||||
$ git diff --name-only <6>
|
||||
$ edit foo.c
|
||||
$ git diff --name-only <7>
|
||||
$ git update-index --no-assume-unchanged foo.c <8>
|
||||
$ git diff --name-only <9>
|
||||
$ git diff --name-only <7>
|
||||
$ git update-index --no-assume-unchanged foo.c <8>
|
||||
$ git diff --name-only <9>
|
||||
M foo.c
|
||||
|
||||
<1> forces lstat(2) to set "assume unchanged" bits for paths
|
||||
that match index.
|
||||
------------
|
||||
+
|
||||
<1> forces lstat(2) to set "assume unchanged" bits for paths that match index.
|
||||
<2> mark the path to be edited.
|
||||
<3> this does lstat(2) and finds index matches the path.
|
||||
<4> this does lstat(2) and finds index does not match the path.
|
||||
<4> this does lstat(2) and finds index does *not* match the path.
|
||||
<5> registering the new version to index sets "assume unchanged" bit.
|
||||
<6> and it is assumed unchanged.
|
||||
<7> even after you edit it.
|
||||
<8> you can tell about the change after the fact.
|
||||
<9> now it checks with lstat(2) and finds it has been changed.
|
||||
------------
|
||||
|
||||
|
||||
Configuration
|
||||
|
|
|
@ -17,7 +17,10 @@ Prints a git logical variable.
|
|||
OPTIONS
|
||||
-------
|
||||
-l::
|
||||
Cause the logical variables to be listed.
|
||||
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-repo-config -l`.)
|
||||
|
||||
EXAMPLE
|
||||
--------
|
||||
|
@ -46,6 +49,7 @@ See Also
|
|||
--------
|
||||
gitlink:git-commit-tree[1]
|
||||
gitlink:git-tag[1]
|
||||
gitlink:git-repo-config[1]
|
||||
|
||||
Author
|
||||
------
|
||||
|
|
|
@ -25,7 +25,7 @@ OPTIONS
|
|||
-v::
|
||||
After verifying the pack, show list of objects contained
|
||||
in the pack.
|
||||
--::
|
||||
\--::
|
||||
Do not interpret any more arguments as options.
|
||||
|
||||
OUTPUT FORMAT
|
||||
|
|
|
@ -58,7 +58,7 @@ git-whatchanged -p v2.6.12.. include/scsi drivers/scsi::
|
|||
Show as patches the commits since version 'v2.6.12' that changed
|
||||
any file in the include/scsi or drivers/scsi subdirectories
|
||||
|
||||
git-whatchanged --since="2 weeks ago" -- gitk::
|
||||
git-whatchanged --since="2 weeks ago" \-- gitk::
|
||||
|
||||
Show the changes during the last two weeks to the file 'gitk'.
|
||||
The "--" is necessary to avoid confusion with the *branch* named
|
||||
|
|
|
@ -31,7 +31,7 @@ gitk v2.6.12.. include/scsi drivers/scsi::
|
|||
Show as the changes since version 'v2.6.12' that changed any
|
||||
file in the include/scsi or drivers/scsi subdirectories
|
||||
|
||||
gitk --since="2 weeks ago" -- gitk::
|
||||
gitk --since="2 weeks ago" \-- gitk::
|
||||
|
||||
Show the changes during the last two weeks to the file 'gitk'.
|
||||
The "--" is necessary to avoid confusion with the *branch* named
|
||||
|
|
|
@ -1,39 +1,71 @@
|
|||
object::
|
||||
The unit of storage in git. It is uniquely identified by
|
||||
the SHA1 of its contents. Consequently, an object can not
|
||||
be changed.
|
||||
alternate object database::
|
||||
Via the alternates mechanism, a repository can inherit part of its
|
||||
object database from another object database, which is called
|
||||
"alternate".
|
||||
|
||||
object name::
|
||||
The unique identifier of an object. The hash of the object's contents
|
||||
using the Secure Hash Algorithm 1 and usually represented by the 40
|
||||
character hexadecimal encoding of the hash of the object (possibly
|
||||
followed by a white space).
|
||||
|
||||
SHA1::
|
||||
Synonym for object name.
|
||||
|
||||
object identifier::
|
||||
Synonym for object name.
|
||||
|
||||
hash::
|
||||
In git's context, synonym to object name.
|
||||
|
||||
object database::
|
||||
Stores a set of "objects", and an individual object is identified
|
||||
by its object name. The objects usually live in `$GIT_DIR/objects/`.
|
||||
bare repository::
|
||||
A bare repository is normally an appropriately named
|
||||
directory with a `.git` suffix that does not have a
|
||||
locally checked-out copy of any of the files under revision
|
||||
control. That is, all of the `git` administrative and
|
||||
control files that would normally be present in the
|
||||
hidden `.git` sub-directory are directly present in
|
||||
the `repository.git` directory instead, and no other files
|
||||
are present and checked out. Usually publishers of public
|
||||
repositories make bare repositories available.
|
||||
|
||||
blob object::
|
||||
Untyped object, e.g. the contents of a file.
|
||||
|
||||
tree object::
|
||||
An object containing a list of file names and modes along with refs
|
||||
to the associated blob and/or tree objects. A tree is equivalent
|
||||
to a directory.
|
||||
branch::
|
||||
A non-cyclical graph of revisions, i.e. the complete history of
|
||||
a particular revision, which is called the branch head. The
|
||||
branch heads are stored in `$GIT_DIR/refs/heads/`.
|
||||
|
||||
tree::
|
||||
Either a working tree, or a tree object together with the
|
||||
dependent blob and tree objects (i.e. a stored representation
|
||||
of a working tree).
|
||||
cache::
|
||||
Obsolete for: index.
|
||||
|
||||
chain::
|
||||
A list of objects, where each object in the list contains a
|
||||
reference to its successor (for example, the successor of a commit
|
||||
could be one of its parents).
|
||||
|
||||
changeset::
|
||||
BitKeeper/cvsps speak for "commit". Since git does not store
|
||||
changes, but states, it really does not make sense to use
|
||||
the term "changesets" with git.
|
||||
|
||||
checkout::
|
||||
The action of updating the working tree to a revision which was
|
||||
stored in the object database.
|
||||
|
||||
cherry-picking::
|
||||
In SCM jargon, "cherry pick" means to choose a subset of
|
||||
changes out of a series of changes (typically commits)
|
||||
and record them as a new series of changes on top of
|
||||
different codebase. In GIT, this is performed by
|
||||
"git cherry-pick" command to extract the change
|
||||
introduced by an existing commit and to record it based
|
||||
on the tip of the current branch as a new commit.
|
||||
|
||||
clean::
|
||||
A working tree is clean, if it corresponds to the revision
|
||||
referenced by the current head. Also see "dirty".
|
||||
|
||||
commit::
|
||||
As a verb: The action of storing the current state of the index in the
|
||||
object database. The result is a revision.
|
||||
As a noun: Short hand for commit object.
|
||||
|
||||
commit object::
|
||||
An object which contains the information about a particular
|
||||
revision, such as parents, committer, author, date and the
|
||||
tree object which corresponds to the top directory of the
|
||||
stored revision.
|
||||
|
||||
core git::
|
||||
Fundamental data structures and utilities of git. Exposes only
|
||||
limited source code management tools.
|
||||
|
||||
DAG::
|
||||
Directed acyclic graph. The commit objects form a directed acyclic
|
||||
|
@ -41,6 +73,63 @@ DAG::
|
|||
objects is acyclic (there is no chain which begins and ends with the
|
||||
same object).
|
||||
|
||||
dircache::
|
||||
You are *waaaaay* behind.
|
||||
|
||||
dirty::
|
||||
A working tree is said to be dirty if it contains modifications
|
||||
which have not been committed to the current branch.
|
||||
|
||||
directory::
|
||||
The list you get with "ls" :-)
|
||||
|
||||
ent::
|
||||
Favorite synonym to "tree-ish" by some total geeks. See
|
||||
`http://en.wikipedia.org/wiki/Ent_(Middle-earth)` for an in-depth
|
||||
explanation.
|
||||
|
||||
fast forward::
|
||||
A fast-forward is a special type of merge where you have
|
||||
a revision and you are "merging" another branch's changes
|
||||
that happen to be a descendant of what you have.
|
||||
In such these cases, you do not make a new merge commit but
|
||||
instead just update to his revision. This will happen
|
||||
frequently on a tracking branch of a remote repository.
|
||||
|
||||
fetch::
|
||||
Fetching a branch means to get the branch's head ref from a
|
||||
remote repository, to find out which objects are missing from
|
||||
the local object database, and to get them, too.
|
||||
|
||||
file system::
|
||||
Linus Torvalds originally designed git to be a user space file
|
||||
system, i.e. the infrastructure to hold files and directories.
|
||||
That ensured the efficiency and speed of git.
|
||||
|
||||
git archive::
|
||||
Synonym for repository (for arch people).
|
||||
|
||||
hash::
|
||||
In git's context, synonym to object name.
|
||||
|
||||
head::
|
||||
The top of a branch. It contains a ref to the corresponding
|
||||
commit object.
|
||||
|
||||
head ref::
|
||||
A ref pointing to a head. Often, this is abbreviated to "head".
|
||||
Head refs are stored in `$GIT_DIR/refs/heads/`.
|
||||
|
||||
hook::
|
||||
During the normal execution of several git commands,
|
||||
call-outs are made to optional scripts that allow
|
||||
a developer to add functionality or checking.
|
||||
Typically, the hooks allow for a command to be pre-verified
|
||||
and potentially aborted, and allow for a post-notification
|
||||
after the operation is done.
|
||||
The hook scripts are found in the `$GIT_DIR/hooks/` directory,
|
||||
and are enabled by simply making them executable.
|
||||
|
||||
index::
|
||||
A collection of files with stat information, whose contents are
|
||||
stored as objects. The index is a stored version of your working
|
||||
|
@ -53,92 +142,167 @@ index entry::
|
|||
yet finished (i.e. if the index contains multiple versions of
|
||||
that file).
|
||||
|
||||
unmerged index:
|
||||
An index which contains unmerged index entries.
|
||||
master::
|
||||
The default development branch. Whenever you create a git
|
||||
repository, a branch named "master" is created, and becomes
|
||||
the active branch. In most cases, this contains the local
|
||||
development, though that is purely conventional and not required.
|
||||
|
||||
cache::
|
||||
Obsolete for: index.
|
||||
merge::
|
||||
To merge branches means to try to accumulate the changes since a
|
||||
common ancestor and apply them to the first branch. An automatic
|
||||
merge uses heuristics to accomplish that. Evidently, an automatic
|
||||
merge can fail.
|
||||
|
||||
working tree::
|
||||
The set of files and directories currently being worked on,
|
||||
i.e. you can work in your working tree without using git at all.
|
||||
object::
|
||||
The unit of storage in git. It is uniquely identified by
|
||||
the SHA1 of its contents. Consequently, an object can not
|
||||
be changed.
|
||||
|
||||
directory::
|
||||
The list you get with "ls" :-)
|
||||
object database::
|
||||
Stores a set of "objects", and an individual object is identified
|
||||
by its object name. The objects usually live in `$GIT_DIR/objects/`.
|
||||
|
||||
revision::
|
||||
A particular state of files and directories which was stored in
|
||||
the object database. It is referenced by a commit object.
|
||||
object identifier::
|
||||
Synonym for object name.
|
||||
|
||||
checkout::
|
||||
The action of updating the working tree to a revision which was
|
||||
stored in the object database.
|
||||
object name::
|
||||
The unique identifier of an object. The hash of the object's contents
|
||||
using the Secure Hash Algorithm 1 and usually represented by the 40
|
||||
character hexadecimal encoding of the hash of the object (possibly
|
||||
followed by a white space).
|
||||
|
||||
commit::
|
||||
As a verb: The action of storing the current state of the index in the
|
||||
object database. The result is a revision.
|
||||
As a noun: Short hand for commit object.
|
||||
object type:
|
||||
One of the identifiers "commit","tree","tag" and "blob" describing
|
||||
the type of an object.
|
||||
|
||||
commit object::
|
||||
An object which contains the information about a particular
|
||||
revision, such as parents, committer, author, date and the
|
||||
tree object which corresponds to the top directory of the
|
||||
stored revision.
|
||||
octopus::
|
||||
To merge more than two branches. Also denotes an intelligent
|
||||
predator.
|
||||
|
||||
origin::
|
||||
The default upstream tracking branch. Most projects have at
|
||||
least one upstream project which they track. By default
|
||||
'origin' is used for that purpose. New upstream updates
|
||||
will be fetched into this branch; you should never commit
|
||||
to it yourself.
|
||||
|
||||
pack::
|
||||
A set of objects which have been compressed into one file (to save
|
||||
space or to transmit them efficiently).
|
||||
|
||||
pack index::
|
||||
The list of identifiers, and other information, of the objects in a
|
||||
pack, to assist in efficiently accessing the contents of a pack.
|
||||
|
||||
parent::
|
||||
A commit object contains a (possibly empty) list of the logical
|
||||
predecessor(s) in the line of development, i.e. its parents.
|
||||
|
||||
changeset::
|
||||
BitKeeper/cvsps speak for "commit". Since git does not store
|
||||
changes, but states, it really does not make sense to use
|
||||
the term "changesets" with git.
|
||||
pickaxe::
|
||||
The term pickaxe refers to an option to the diffcore routines
|
||||
that help select changes that add or delete a given text string.
|
||||
With the --pickaxe-all option, it can be used to view the
|
||||
full changeset that introduced or removed, say, a particular
|
||||
line of text. See gitlink:git-diff[1].
|
||||
|
||||
clean::
|
||||
A working tree is clean, if it corresponds to the revision
|
||||
referenced by the current head.
|
||||
plumbing::
|
||||
Cute name for core git.
|
||||
|
||||
dirty::
|
||||
A working tree is said to be dirty if it contains modifications
|
||||
which have not been committed to the current branch.
|
||||
porcelain::
|
||||
Cute name for programs and program suites depending on core git,
|
||||
presenting a high level access to core git. Porcelains expose
|
||||
more of a SCM interface than the plumbing.
|
||||
|
||||
head::
|
||||
The top of a branch. It contains a ref to the corresponding
|
||||
commit object.
|
||||
pull::
|
||||
Pulling a branch means to fetch it and merge it.
|
||||
|
||||
branch::
|
||||
A non-cyclical graph of revisions, i.e. the complete history of
|
||||
a particular revision, which is called the branch head. The
|
||||
branch heads are stored in `$GIT_DIR/refs/heads/`.
|
||||
push::
|
||||
Pushing a branch means to get the branch's head ref from a remote
|
||||
repository, find out if it is an ancestor to the branch's local
|
||||
head ref is a direct, and in that case, putting all objects, which
|
||||
are reachable from the local head ref, and which are missing from
|
||||
the remote repository, into the remote object database, and updating
|
||||
the remote head ref. If the remote head is not an ancestor to the
|
||||
local head, the push fails.
|
||||
|
||||
master::
|
||||
The default branch. Whenever you create a git repository, a branch
|
||||
named "master" is created, and becomes the active branch. In most
|
||||
cases, this contains the local development.
|
||||
reachable::
|
||||
An object is reachable from a ref/commit/tree/tag, if there is a
|
||||
chain leading from the latter to the former.
|
||||
|
||||
origin::
|
||||
The default upstream branch. Most projects have one upstream
|
||||
project which they track, and by default 'origin' is used for
|
||||
that purpose. New updates from upstream will be fetched into
|
||||
this branch; you should never commit to it yourself.
|
||||
rebase::
|
||||
To clean a branch by starting from the head of the main line of
|
||||
development ("master"), and reapply the (possibly cherry-picked)
|
||||
changes from that branch.
|
||||
|
||||
ref::
|
||||
A 40-byte hex representation of a SHA1 pointing to a particular
|
||||
object. These may be stored in `$GIT_DIR/refs/`.
|
||||
A 40-byte hex representation of a SHA1 or a name that denotes
|
||||
a particular object. These may be stored in `$GIT_DIR/refs/`.
|
||||
|
||||
head ref::
|
||||
A ref pointing to a head. Often, this is abbreviated to "head".
|
||||
Head refs are stored in `$GIT_DIR/refs/heads/`.
|
||||
refspec::
|
||||
A refspec is used by fetch and push to describe the mapping
|
||||
between remote ref and local ref. They are combined with
|
||||
a colon in the format <src>:<dst>, preceded by an optional
|
||||
plus sign, +. For example:
|
||||
`git fetch $URL refs/heads/master:refs/heads/origin`
|
||||
means "grab the master branch head from the $URL and store
|
||||
it as my origin branch head".
|
||||
And `git push $URL refs/heads/master:refs/heads/to-upstream`
|
||||
means "publish my master branch head as to-upstream master head
|
||||
at $URL". See also gitlink:git-push[1]
|
||||
|
||||
repository::
|
||||
A collection of refs together with an object database containing
|
||||
all objects, which are reachable from the refs, possibly accompanied
|
||||
by meta data from one or more porcelains. A repository can
|
||||
share an object database with other repositories.
|
||||
|
||||
resolve::
|
||||
The action of fixing up manually what a failed automatic merge
|
||||
left behind.
|
||||
|
||||
revision::
|
||||
A particular state of files and directories which was stored in
|
||||
the object database. It is referenced by a commit object.
|
||||
|
||||
rewind::
|
||||
To throw away part of the development, i.e. to assign the head to
|
||||
an earlier revision.
|
||||
|
||||
SCM::
|
||||
Source code management (tool).
|
||||
|
||||
SHA1::
|
||||
Synonym for object name.
|
||||
|
||||
topic branch::
|
||||
A regular git branch that is used by a developer to
|
||||
identify a conceptual line of development. Since branches
|
||||
are very easy and inexpensive, it is often desirable to
|
||||
have several small branches that each contain very well
|
||||
defined concepts or small incremental yet related changes.
|
||||
|
||||
tracking branch::
|
||||
A regular git branch that is used to follow changes from
|
||||
another repository. A tracking branch should not contain
|
||||
direct modifications or have local commits made to it.
|
||||
A tracking branch can usually be identified as the
|
||||
right-hand-side ref in a Pull: refspec.
|
||||
|
||||
tree object::
|
||||
An object containing a list of file names and modes along with refs
|
||||
to the associated blob and/or tree objects. A tree is equivalent
|
||||
to a directory.
|
||||
|
||||
tree::
|
||||
Either a working tree, or a tree object together with the
|
||||
dependent blob and tree objects (i.e. a stored representation
|
||||
of a working tree).
|
||||
|
||||
tree-ish::
|
||||
A ref pointing to either a commit object, a tree object, or a
|
||||
tag object pointing to a tag or commit or tree object.
|
||||
|
||||
ent::
|
||||
Favorite synonym to "tree-ish" by some total geeks. See
|
||||
`http://en.wikipedia.org/wiki/Ent_(Middle-earth)` for an in-depth
|
||||
explanation.
|
||||
|
||||
tag object::
|
||||
An object containing a ref pointing to another object, which can
|
||||
contain a message just like a commit object. It can also
|
||||
|
@ -153,101 +317,10 @@ tag::
|
|||
A tag is most typically used to mark a particular point in the
|
||||
commit ancestry chain.
|
||||
|
||||
merge::
|
||||
To merge branches means to try to accumulate the changes since a
|
||||
common ancestor and apply them to the first branch. An automatic
|
||||
merge uses heuristics to accomplish that. Evidently, an automatic
|
||||
merge can fail.
|
||||
unmerged index:
|
||||
An index which contains unmerged index entries.
|
||||
|
||||
octopus::
|
||||
To merge more than two branches. Also denotes an intelligent
|
||||
predator.
|
||||
|
||||
resolve::
|
||||
The action of fixing up manually what a failed automatic merge
|
||||
left behind.
|
||||
|
||||
rewind::
|
||||
To throw away part of the development, i.e. to assign the head to
|
||||
an earlier revision.
|
||||
|
||||
rebase::
|
||||
To clean a branch by starting from the head of the main line of
|
||||
development ("master"), and reapply the (possibly cherry-picked)
|
||||
changes from that branch.
|
||||
|
||||
repository::
|
||||
A collection of refs together with an object database containing
|
||||
all objects, which are reachable from the refs, possibly accompanied
|
||||
by meta data from one or more porcelains. A repository can
|
||||
share an object database with other repositories.
|
||||
|
||||
git archive::
|
||||
Synonym for repository (for arch people).
|
||||
|
||||
file system::
|
||||
Linus Torvalds originally designed git to be a user space file
|
||||
system, i.e. the infrastructure to hold files and directories.
|
||||
That ensured the efficiency and speed of git.
|
||||
|
||||
alternate object database::
|
||||
Via the alternates mechanism, a repository can inherit part of its
|
||||
object database from another object database, which is called
|
||||
"alternate".
|
||||
|
||||
reachable::
|
||||
An object is reachable from a ref/commit/tree/tag, if there is a
|
||||
chain leading from the latter to the former.
|
||||
|
||||
chain::
|
||||
A list of objects, where each object in the list contains a
|
||||
reference to its successor (for example, the successor of a commit
|
||||
could be one of its parents).
|
||||
|
||||
fetch::
|
||||
Fetching a branch means to get the branch's head ref from a
|
||||
remote repository, to find out which objects are missing from
|
||||
the local object database, and to get them, too.
|
||||
|
||||
pull::
|
||||
Pulling a branch means to fetch it and merge it.
|
||||
|
||||
push::
|
||||
Pushing a branch means to get the branch's head ref from a remote
|
||||
repository, find out if it is an ancestor to the branch's local
|
||||
head ref is a direct, and in that case, putting all objects, which
|
||||
are reachable from the local head ref, and which are missing from
|
||||
the remote repository, into the remote object database, and updating
|
||||
the remote head ref. If the remote head is not an ancestor to the
|
||||
local head, the push fails.
|
||||
|
||||
pack::
|
||||
A set of objects which have been compressed into one file (to save
|
||||
space or to transmit them efficiently).
|
||||
|
||||
pack index::
|
||||
The list of identifiers, and other information, of the objects in a
|
||||
pack, to assist in efficiently accessing the contents of a pack.
|
||||
|
||||
core git::
|
||||
Fundamental data structures and utilities of git. Exposes only
|
||||
limited source code management tools.
|
||||
|
||||
plumbing::
|
||||
Cute name for core git.
|
||||
|
||||
porcelain::
|
||||
Cute name for programs and program suites depending on core git,
|
||||
presenting a high level access to core git. Porcelains expose
|
||||
more of a SCM interface than the plumbing.
|
||||
|
||||
object type:
|
||||
One of the identifiers "commit","tree","tag" and "blob" describing
|
||||
the type of an object.
|
||||
|
||||
SCM::
|
||||
Source code management (tool).
|
||||
|
||||
dircache::
|
||||
You are *waaaaay* behind.
|
||||
working tree::
|
||||
The set of files and directories currently being worked on,
|
||||
i.e. you can work in your working tree without using git at all.
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ ($)
|
|||
';
|
||||
|
||||
@keys=sort {uc($a) cmp uc($b)} keys %terms;
|
||||
$pattern='(\b'.join('\b|\b',reverse @keys).'\b)';
|
||||
$pattern='(\b(?<!link:git-)'.join('\b|\b(?<!link:git-)',reverse @keys).'\b)';
|
||||
foreach $key (@keys) {
|
||||
$terms{$key}=~s/$pattern/sprintf "<<ref_".no_spaces($1).",$1>>";/eg;
|
||||
print '[[ref_'.no_spaces($key).']]'.$key."::\n"
|
||||
|
|
36
Makefile
36
Makefile
|
@ -28,8 +28,8 @@ all:
|
|||
#
|
||||
# Define NO_SETENV if you don't have setenv in the C library.
|
||||
#
|
||||
# Define USE_SYMLINK_HEAD if you want .git/HEAD to be a symbolic link.
|
||||
# Don't enable it on Windows.
|
||||
# Define NO_SYMLINK_HEAD if you never want .git/HEAD to be a symbolic link.
|
||||
# Enable it on Windows. By default, symrefs are still used.
|
||||
#
|
||||
# Define PPC_SHA1 environment variable when running make to make use of
|
||||
# a bundled SHA1 routine optimized for PowerPC.
|
||||
|
@ -115,13 +115,13 @@ SPARSE_FLAGS = -D__BIG_ENDIAN__ -D__powerpc__
|
|||
SCRIPT_SH = \
|
||||
git-add.sh git-bisect.sh git-branch.sh git-checkout.sh \
|
||||
git-cherry.sh git-clean.sh git-clone.sh git-commit.sh \
|
||||
git-count-objects.sh git-diff.sh git-fetch.sh \
|
||||
git-fetch.sh \
|
||||
git-format-patch.sh git-ls-remote.sh \
|
||||
git-merge-one-file.sh git-parse-remote.sh \
|
||||
git-prune.sh git-pull.sh git-push.sh git-rebase.sh \
|
||||
git-prune.sh git-pull.sh git-rebase.sh \
|
||||
git-repack.sh git-request-pull.sh git-reset.sh \
|
||||
git-resolve.sh git-revert.sh git-rm.sh git-sh-setup.sh \
|
||||
git-tag.sh git-verify-tag.sh git-whatchanged.sh \
|
||||
git-tag.sh git-verify-tag.sh \
|
||||
git-applymbox.sh git-applypatch.sh git-am.sh \
|
||||
git-merge.sh git-merge-stupid.sh git-merge-octopus.sh \
|
||||
git-merge-resolve.sh git-merge-ours.sh git-grep.sh \
|
||||
|
@ -139,7 +139,7 @@ SCRIPT_PYTHON = \
|
|||
SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
|
||||
$(patsubst %.perl,%,$(SCRIPT_PERL)) \
|
||||
$(patsubst %.py,%,$(SCRIPT_PYTHON)) \
|
||||
git-cherry-pick git-show git-status
|
||||
git-cherry-pick git-status
|
||||
|
||||
# The ones that do not have to link with lcrypto, lz nor xdiff.
|
||||
SIMPLE_PROGRAMS = \
|
||||
|
@ -167,7 +167,8 @@ PROGRAMS = \
|
|||
git-name-rev$X git-pack-redundant$X git-repo-config$X git-var$X \
|
||||
git-describe$X git-merge-tree$X git-blame$X git-imap-send$X
|
||||
|
||||
BUILT_INS = git-log$X
|
||||
BUILT_INS = git-log$X git-whatchanged$X git-show$X \
|
||||
git-count-objects$X git-diff$X git-push$X
|
||||
|
||||
# what 'all' will build and 'install' will install, in gitexecdir
|
||||
ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS)
|
||||
|
@ -199,7 +200,7 @@ LIB_H = \
|
|||
tree-walk.h log-tree.h
|
||||
|
||||
DIFF_OBJS = \
|
||||
diff-lib.o diffcore-break.o diffcore-order.o \
|
||||
diff.o diff-lib.o diffcore-break.o diffcore-order.o \
|
||||
diffcore-pickaxe.o diffcore-rename.o tree-diff.o combine-diff.o \
|
||||
diffcore-delta.o log-tree.o
|
||||
|
||||
|
@ -214,7 +215,7 @@ LIB_OBJS = \
|
|||
$(DIFF_OBJS)
|
||||
|
||||
BUILTIN_OBJS = \
|
||||
builtin-log.o builtin-help.o
|
||||
builtin-log.o builtin-help.o builtin-count.o builtin-diff.o builtin-push.o
|
||||
|
||||
GITLIBS = $(LIB_FILE) $(XDIFF_LIB)
|
||||
LIBS = $(GITLIBS) -lz
|
||||
|
@ -263,6 +264,7 @@ ifeq ($(uname_O),Cygwin)
|
|||
NO_D_TYPE_IN_DIRENT = YesPlease
|
||||
NO_D_INO_IN_DIRENT = YesPlease
|
||||
NO_STRCASESTR = YesPlease
|
||||
NO_SYMLINK_HEAD = YesPlease
|
||||
NEEDS_LIBICONV = YesPlease
|
||||
# There are conflicting reports about this.
|
||||
# On some boxes NO_MMAP is needed, and not so elsewhere.
|
||||
|
@ -386,6 +388,9 @@ endif
|
|||
ifdef NO_D_INO_IN_DIRENT
|
||||
ALL_CFLAGS += -DNO_D_INO_IN_DIRENT
|
||||
endif
|
||||
ifdef NO_SYMLINK_HEAD
|
||||
ALL_CFLAGS += -DNO_SYMLINK_HEAD
|
||||
endif
|
||||
ifdef NO_STRCASESTR
|
||||
COMPAT_CFLAGS += -DNO_STRCASESTR
|
||||
COMPAT_OBJS += compat/strcasestr.o
|
||||
|
@ -470,6 +475,8 @@ git$X: git.c common-cmds.h $(BUILTIN_OBJS) $(GITLIBS)
|
|||
$(ALL_CFLAGS) -o $@ $(filter %.c,$^) \
|
||||
$(BUILTIN_OBJS) $(ALL_LDFLAGS) $(LIBS)
|
||||
|
||||
builtin-help.o: common-cmds.h
|
||||
|
||||
$(BUILT_INS): git$X
|
||||
rm -f $@ && ln git$X $@
|
||||
|
||||
|
@ -503,9 +510,6 @@ $(patsubst %.py,%,$(SCRIPT_PYTHON)) : % : %.py
|
|||
git-cherry-pick: git-revert
|
||||
cp $< $@
|
||||
|
||||
git-show: git-whatchanged
|
||||
cp $< $@
|
||||
|
||||
git-status: git-commit
|
||||
cp $< $@
|
||||
|
||||
|
@ -560,10 +564,6 @@ git-http-push$X: revision.o http.o http-push.o $(LIB_FILE)
|
|||
$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
|
||||
$(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
|
||||
|
||||
git-rev-list$X: rev-list.o $(LIB_FILE)
|
||||
$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
|
||||
$(LIBS) $(OPENSSL_LIBSSL)
|
||||
|
||||
init-db.o: init-db.c
|
||||
$(CC) -c $(ALL_CFLAGS) \
|
||||
-DDEFAULT_GIT_TEMPLATE_DIR='"$(template_dir_SQ)"' $*.c
|
||||
|
@ -573,12 +573,12 @@ $(patsubst git-%$X,%.o,$(PROGRAMS)): $(GITLIBS)
|
|||
$(DIFF_OBJS): diffcore.h
|
||||
|
||||
$(LIB_FILE): $(LIB_OBJS)
|
||||
$(AR) rcs $@ $(LIB_OBJS)
|
||||
rm -f $@ && $(AR) rcs $@ $(LIB_OBJS)
|
||||
|
||||
XDIFF_OBJS=xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o
|
||||
|
||||
$(XDIFF_LIB): $(XDIFF_OBJS)
|
||||
$(AR) rcs $@ $(XDIFF_OBJS)
|
||||
rm -f $@ && $(AR) rcs $@ $(XDIFF_OBJS)
|
||||
|
||||
|
||||
doc:
|
||||
|
|
10
blame.c
10
blame.c
|
@ -515,9 +515,9 @@ static int compare_tree_path(struct rev_info* revs,
|
|||
paths[1] = NULL;
|
||||
|
||||
diff_tree_setup_paths(get_pathspec(revs->prefix, paths),
|
||||
&revs->diffopt);
|
||||
&revs->pruning);
|
||||
ret = rev_compare_tree(revs, c1->tree, c2->tree);
|
||||
diff_tree_release_paths(&revs->diffopt);
|
||||
diff_tree_release_paths(&revs->pruning);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -531,9 +531,9 @@ static int same_tree_as_empty_path(struct rev_info *revs, struct tree* t1,
|
|||
paths[1] = NULL;
|
||||
|
||||
diff_tree_setup_paths(get_pathspec(revs->prefix, paths),
|
||||
&revs->diffopt);
|
||||
&revs->pruning);
|
||||
ret = rev_same_tree_as_empty(revs, t1);
|
||||
diff_tree_release_paths(&revs->diffopt);
|
||||
diff_tree_release_paths(&revs->pruning);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -834,7 +834,7 @@ int main(int argc, const char **argv)
|
|||
|
||||
args[0] = filename;
|
||||
args[1] = NULL;
|
||||
diff_tree_setup_paths(args, &rev.diffopt);
|
||||
diff_tree_setup_paths(args, &rev.pruning);
|
||||
prepare_revision_walk(&rev);
|
||||
process_commits(&rev, filename, &initial);
|
||||
|
||||
|
|
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* Builtin "git count-objects".
|
||||
*
|
||||
* Copyright (c) 2006 Junio C Hamano
|
||||
*/
|
||||
|
||||
#include "cache.h"
|
||||
#include "builtin.h"
|
||||
|
||||
static const char count_objects_usage[] = "git-count-objects [-v]";
|
||||
|
||||
static void count_objects(DIR *d, char *path, int len, int verbose,
|
||||
unsigned long *loose,
|
||||
unsigned long *loose_size,
|
||||
unsigned long *packed_loose,
|
||||
unsigned long *garbage)
|
||||
{
|
||||
struct dirent *ent;
|
||||
while ((ent = readdir(d)) != NULL) {
|
||||
char hex[41];
|
||||
unsigned char sha1[20];
|
||||
const char *cp;
|
||||
int bad = 0;
|
||||
|
||||
if ((ent->d_name[0] == '.') &&
|
||||
(ent->d_name[1] == 0 ||
|
||||
((ent->d_name[1] == '.') && (ent->d_name[2] == 0))))
|
||||
continue;
|
||||
for (cp = ent->d_name; *cp; cp++) {
|
||||
int ch = *cp;
|
||||
if (('0' <= ch && ch <= '9') ||
|
||||
('a' <= ch && ch <= 'f'))
|
||||
continue;
|
||||
bad = 1;
|
||||
break;
|
||||
}
|
||||
if (cp - ent->d_name != 38)
|
||||
bad = 1;
|
||||
else {
|
||||
struct stat st;
|
||||
memcpy(path + len + 3, ent->d_name, 38);
|
||||
path[len + 2] = '/';
|
||||
path[len + 41] = 0;
|
||||
if (lstat(path, &st) || !S_ISREG(st.st_mode))
|
||||
bad = 1;
|
||||
else
|
||||
(*loose_size) += st.st_blocks;
|
||||
}
|
||||
if (bad) {
|
||||
if (verbose) {
|
||||
error("garbage found: %.*s/%s",
|
||||
len + 2, path, ent->d_name);
|
||||
(*garbage)++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
(*loose)++;
|
||||
if (!verbose)
|
||||
continue;
|
||||
memcpy(hex, path+len, 2);
|
||||
memcpy(hex+2, ent->d_name, 38);
|
||||
hex[40] = 0;
|
||||
if (get_sha1_hex(hex, sha1))
|
||||
die("internal error");
|
||||
if (has_sha1_pack(sha1))
|
||||
(*packed_loose)++;
|
||||
}
|
||||
}
|
||||
|
||||
int cmd_count_objects(int ac, const char **av, char **ep)
|
||||
{
|
||||
int i;
|
||||
int verbose = 0;
|
||||
const char *objdir = get_object_directory();
|
||||
int len = strlen(objdir);
|
||||
char *path = xmalloc(len + 50);
|
||||
unsigned long loose = 0, packed = 0, packed_loose = 0, garbage = 0;
|
||||
unsigned long loose_size = 0;
|
||||
|
||||
for (i = 1; i < ac; i++) {
|
||||
const char *arg = av[i];
|
||||
if (*arg != '-')
|
||||
break;
|
||||
else if (!strcmp(arg, "-v"))
|
||||
verbose = 1;
|
||||
else
|
||||
usage(count_objects_usage);
|
||||
}
|
||||
|
||||
/* we do not take arguments other than flags for now */
|
||||
if (i < ac)
|
||||
usage(count_objects_usage);
|
||||
memcpy(path, objdir, len);
|
||||
if (len && objdir[len-1] != '/')
|
||||
path[len++] = '/';
|
||||
for (i = 0; i < 256; i++) {
|
||||
DIR *d;
|
||||
sprintf(path + len, "%02x", i);
|
||||
d = opendir(path);
|
||||
if (!d)
|
||||
continue;
|
||||
count_objects(d, path, len, verbose,
|
||||
&loose, &loose_size, &packed_loose, &garbage);
|
||||
closedir(d);
|
||||
}
|
||||
if (verbose) {
|
||||
struct packed_git *p;
|
||||
if (!packed_git)
|
||||
prepare_packed_git();
|
||||
for (p = packed_git; p; p = p->next) {
|
||||
if (!p->pack_local)
|
||||
continue;
|
||||
packed += num_packed_objects(p);
|
||||
}
|
||||
printf("count: %lu\n", loose);
|
||||
printf("size: %lu\n", loose_size / 2);
|
||||
printf("in-pack: %lu\n", packed);
|
||||
printf("prune-packable: %lu\n", packed_loose);
|
||||
printf("garbage: %lu\n", garbage);
|
||||
}
|
||||
else
|
||||
printf("%lu objects, %lu kilobytes\n",
|
||||
loose, loose_size / 2);
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,369 @@
|
|||
/*
|
||||
* Builtin "git diff"
|
||||
*
|
||||
* Copyright (c) 2006 Junio C Hamano
|
||||
*/
|
||||
#include "cache.h"
|
||||
#include "commit.h"
|
||||
#include "blob.h"
|
||||
#include "tag.h"
|
||||
#include "diff.h"
|
||||
#include "diffcore.h"
|
||||
#include "revision.h"
|
||||
#include "log-tree.h"
|
||||
#include "builtin.h"
|
||||
|
||||
/* NEEDSWORK: struct object has place for name but we _do_
|
||||
* know mode when we extracted the blob out of a tree, which
|
||||
* we currently lose.
|
||||
*/
|
||||
struct blobinfo {
|
||||
unsigned char sha1[20];
|
||||
const char *name;
|
||||
};
|
||||
|
||||
static const char builtin_diff_usage[] =
|
||||
"diff <options> <rev>{0,2} -- <path>*";
|
||||
|
||||
static int builtin_diff_files(struct rev_info *revs,
|
||||
int argc, const char **argv)
|
||||
{
|
||||
int silent = 0;
|
||||
while (1 < argc) {
|
||||
const char *arg = argv[1];
|
||||
if (!strcmp(arg, "--base"))
|
||||
revs->max_count = 1;
|
||||
else if (!strcmp(arg, "--ours"))
|
||||
revs->max_count = 2;
|
||||
else if (!strcmp(arg, "--theirs"))
|
||||
revs->max_count = 3;
|
||||
else if (!strcmp(arg, "-q"))
|
||||
silent = 1;
|
||||
else if (!strcmp(arg, "--raw"))
|
||||
revs->diffopt.output_format = DIFF_FORMAT_RAW;
|
||||
else
|
||||
usage(builtin_diff_usage);
|
||||
argv++; argc--;
|
||||
}
|
||||
/*
|
||||
* Make sure there are NO revision (i.e. pending object) parameter,
|
||||
* specified rev.max_count is reasonable (0 <= n <= 3), and
|
||||
* there is no other revision filtering parameter.
|
||||
*/
|
||||
if (revs->pending_objects ||
|
||||
revs->min_age != -1 ||
|
||||
revs->max_age != -1 ||
|
||||
3 < revs->max_count)
|
||||
usage(builtin_diff_usage);
|
||||
if (revs->max_count < 0 &&
|
||||
(revs->diffopt.output_format == DIFF_FORMAT_PATCH))
|
||||
revs->combine_merges = revs->dense_combined_merges = 1;
|
||||
/*
|
||||
* Backward compatibility wart - "diff-files -s" used to
|
||||
* defeat the common diff option "-s" which asked for
|
||||
* DIFF_FORMAT_NO_OUTPUT.
|
||||
*/
|
||||
if (revs->diffopt.output_format == DIFF_FORMAT_NO_OUTPUT)
|
||||
revs->diffopt.output_format = DIFF_FORMAT_RAW;
|
||||
return run_diff_files(revs, silent);
|
||||
}
|
||||
|
||||
static void stuff_change(struct diff_options *opt,
|
||||
unsigned old_mode, unsigned new_mode,
|
||||
const unsigned char *old_sha1,
|
||||
const unsigned char *new_sha1,
|
||||
const char *old_name,
|
||||
const char *new_name)
|
||||
{
|
||||
struct diff_filespec *one, *two;
|
||||
|
||||
if (memcmp(null_sha1, old_sha1, 20) &&
|
||||
memcmp(null_sha1, new_sha1, 20) &&
|
||||
!memcmp(old_sha1, new_sha1, 20))
|
||||
return;
|
||||
|
||||
if (opt->reverse_diff) {
|
||||
unsigned tmp;
|
||||
const
|
||||
const unsigned char *tmp_u;
|
||||
const char *tmp_c;
|
||||
tmp = old_mode; old_mode = new_mode; new_mode = tmp;
|
||||
tmp_u = old_sha1; old_sha1 = new_sha1; new_sha1 = tmp_u;
|
||||
tmp_c = old_name; old_name = new_name; new_name = tmp_c;
|
||||
}
|
||||
one = alloc_filespec(old_name);
|
||||
two = alloc_filespec(new_name);
|
||||
fill_filespec(one, old_sha1, old_mode);
|
||||
fill_filespec(two, new_sha1, new_mode);
|
||||
|
||||
/* NEEDSWORK: shouldn't this part of diffopt??? */
|
||||
diff_queue(&diff_queued_diff, one, two);
|
||||
}
|
||||
|
||||
static int builtin_diff_b_f(struct rev_info *revs,
|
||||
int argc, const char **argv,
|
||||
struct blobinfo *blob,
|
||||
const char *path)
|
||||
{
|
||||
/* Blob vs file in the working tree*/
|
||||
struct stat st;
|
||||
|
||||
while (1 < argc) {
|
||||
const char *arg = argv[1];
|
||||
if (!strcmp(arg, "--raw"))
|
||||
revs->diffopt.output_format = DIFF_FORMAT_RAW;
|
||||
else
|
||||
usage(builtin_diff_usage);
|
||||
argv++; argc--;
|
||||
}
|
||||
if (lstat(path, &st))
|
||||
die("'%s': %s", path, strerror(errno));
|
||||
if (!(S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)))
|
||||
die("'%s': not a regular file or symlink", path);
|
||||
stuff_change(&revs->diffopt,
|
||||
canon_mode(st.st_mode), canon_mode(st.st_mode),
|
||||
blob[0].sha1, null_sha1,
|
||||
blob[0].name, path);
|
||||
diffcore_std(&revs->diffopt);
|
||||
diff_flush(&revs->diffopt);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int builtin_diff_blobs(struct rev_info *revs,
|
||||
int argc, const char **argv,
|
||||
struct blobinfo *blob)
|
||||
{
|
||||
/* Blobs */
|
||||
unsigned mode = canon_mode(S_IFREG | 0644);
|
||||
|
||||
while (1 < argc) {
|
||||
const char *arg = argv[1];
|
||||
if (!strcmp(arg, "--raw"))
|
||||
revs->diffopt.output_format = DIFF_FORMAT_RAW;
|
||||
else
|
||||
usage(builtin_diff_usage);
|
||||
argv++; argc--;
|
||||
}
|
||||
stuff_change(&revs->diffopt,
|
||||
mode, mode,
|
||||
blob[0].sha1, blob[1].sha1,
|
||||
blob[1].name, blob[1].name);
|
||||
diffcore_std(&revs->diffopt);
|
||||
diff_flush(&revs->diffopt);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int builtin_diff_index(struct rev_info *revs,
|
||||
int argc, const char **argv)
|
||||
{
|
||||
int cached = 0;
|
||||
while (1 < argc) {
|
||||
const char *arg = argv[1];
|
||||
if (!strcmp(arg, "--cached"))
|
||||
cached = 1;
|
||||
else if (!strcmp(arg, "--raw"))
|
||||
revs->diffopt.output_format = DIFF_FORMAT_RAW;
|
||||
else
|
||||
usage(builtin_diff_usage);
|
||||
argv++; argc--;
|
||||
}
|
||||
/*
|
||||
* Make sure there is one revision (i.e. pending object),
|
||||
* and there is no revision filtering parameters.
|
||||
*/
|
||||
if (!revs->pending_objects || revs->pending_objects->next ||
|
||||
revs->max_count != -1 || revs->min_age != -1 ||
|
||||
revs->max_age != -1)
|
||||
usage(builtin_diff_usage);
|
||||
return run_diff_index(revs, cached);
|
||||
}
|
||||
|
||||
static int builtin_diff_tree(struct rev_info *revs,
|
||||
int argc, const char **argv,
|
||||
struct object_list *ent)
|
||||
{
|
||||
const unsigned char *(sha1[2]);
|
||||
int swap = 1;
|
||||
while (1 < argc) {
|
||||
const char *arg = argv[1];
|
||||
if (!strcmp(arg, "--raw"))
|
||||
revs->diffopt.output_format = DIFF_FORMAT_RAW;
|
||||
else
|
||||
usage(builtin_diff_usage);
|
||||
argv++; argc--;
|
||||
}
|
||||
|
||||
/* We saw two trees, ent[0] and ent[1].
|
||||
* unless ent[0] is unintesting, they are swapped
|
||||
*/
|
||||
if (ent[0].item->flags & UNINTERESTING)
|
||||
swap = 0;
|
||||
sha1[swap] = ent[0].item->sha1;
|
||||
sha1[1-swap] = ent[1].item->sha1;
|
||||
diff_tree_sha1(sha1[0], sha1[1], "", &revs->diffopt);
|
||||
log_tree_diff_flush(revs);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int builtin_diff_combined(struct rev_info *revs,
|
||||
int argc, const char **argv,
|
||||
struct object_list *ent,
|
||||
int ents)
|
||||
{
|
||||
const unsigned char (*parent)[20];
|
||||
int i;
|
||||
|
||||
while (1 < argc) {
|
||||
const char *arg = argv[1];
|
||||
if (!strcmp(arg, "--raw"))
|
||||
revs->diffopt.output_format = DIFF_FORMAT_RAW;
|
||||
else
|
||||
usage(builtin_diff_usage);
|
||||
argv++; argc--;
|
||||
}
|
||||
if (!revs->dense_combined_merges && !revs->combine_merges)
|
||||
revs->dense_combined_merges = revs->combine_merges = 1;
|
||||
parent = xmalloc(ents * sizeof(*parent));
|
||||
/* Again, the revs are all reverse */
|
||||
for (i = 0; i < ents; i++)
|
||||
memcpy(parent + i, ent[ents - 1 - i].item->sha1, 20);
|
||||
diff_tree_combined(parent[0], parent + 1, ents - 1,
|
||||
revs->dense_combined_merges, revs);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void add_head(struct rev_info *revs)
|
||||
{
|
||||
unsigned char sha1[20];
|
||||
struct object *obj;
|
||||
if (get_sha1("HEAD", sha1))
|
||||
return;
|
||||
obj = parse_object(sha1);
|
||||
if (!obj)
|
||||
return;
|
||||
add_object(obj, &revs->pending_objects, NULL, "HEAD");
|
||||
}
|
||||
|
||||
int cmd_diff(int argc, const char **argv, char **envp)
|
||||
{
|
||||
struct rev_info rev;
|
||||
struct object_list *list, ent[100];
|
||||
int ents = 0, blobs = 0, paths = 0;
|
||||
const char *path = NULL;
|
||||
struct blobinfo blob[2];
|
||||
|
||||
/*
|
||||
* We could get N tree-ish in the rev.pending_objects list.
|
||||
* Also there could be M blobs there, and P pathspecs.
|
||||
*
|
||||
* N=0, M=0:
|
||||
* cache vs files (diff-files)
|
||||
* N=0, M=2:
|
||||
* compare two random blobs. P must be zero.
|
||||
* N=0, M=1, P=1:
|
||||
* compare a blob with a working tree file.
|
||||
*
|
||||
* N=1, M=0:
|
||||
* tree vs cache (diff-index --cached)
|
||||
*
|
||||
* N=2, M=0:
|
||||
* tree vs tree (diff-tree)
|
||||
*
|
||||
* Other cases are errors.
|
||||
*/
|
||||
|
||||
git_config(git_diff_config);
|
||||
init_revisions(&rev);
|
||||
rev.diffopt.output_format = DIFF_FORMAT_PATCH;
|
||||
|
||||
argc = setup_revisions(argc, argv, &rev, NULL);
|
||||
/* Do we have --cached and not have a pending object, then
|
||||
* default to HEAD by hand. Eek.
|
||||
*/
|
||||
if (!rev.pending_objects) {
|
||||
int i;
|
||||
for (i = 1; i < argc; i++) {
|
||||
const char *arg = argv[i];
|
||||
if (!strcmp(arg, "--"))
|
||||
break;
|
||||
else if (!strcmp(arg, "--cached")) {
|
||||
add_head(&rev);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (list = rev.pending_objects; list; list = list->next) {
|
||||
struct object *obj = list->item;
|
||||
const char *name = list->name;
|
||||
int flags = (obj->flags & UNINTERESTING);
|
||||
if (!obj->parsed)
|
||||
obj = parse_object(obj->sha1);
|
||||
obj = deref_tag(obj, NULL, 0);
|
||||
if (!obj)
|
||||
die("invalid object '%s' given.", name);
|
||||
if (!strcmp(obj->type, commit_type))
|
||||
obj = &((struct commit *)obj)->tree->object;
|
||||
if (!strcmp(obj->type, tree_type)) {
|
||||
if (ARRAY_SIZE(ent) <= ents)
|
||||
die("more than %d trees given: '%s'",
|
||||
(int) ARRAY_SIZE(ent), name);
|
||||
obj->flags |= flags;
|
||||
ent[ents].item = obj;
|
||||
ent[ents].name = name;
|
||||
ents++;
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(obj->type, blob_type)) {
|
||||
if (2 <= blobs)
|
||||
die("more than two blobs given: '%s'", name);
|
||||
memcpy(blob[blobs].sha1, obj->sha1, 20);
|
||||
blob[blobs].name = name;
|
||||
blobs++;
|
||||
continue;
|
||||
|
||||
}
|
||||
die("unhandled object '%s' given.", name);
|
||||
}
|
||||
if (rev.prune_data) {
|
||||
const char **pathspec = rev.prune_data;
|
||||
while (*pathspec) {
|
||||
if (!path)
|
||||
path = *pathspec;
|
||||
paths++;
|
||||
pathspec++;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Now, do the arguments look reasonable?
|
||||
*/
|
||||
if (!ents) {
|
||||
switch (blobs) {
|
||||
case 0:
|
||||
return builtin_diff_files(&rev, argc, argv);
|
||||
break;
|
||||
case 1:
|
||||
if (paths != 1)
|
||||
usage(builtin_diff_usage);
|
||||
return builtin_diff_b_f(&rev, argc, argv, blob, path);
|
||||
break;
|
||||
case 2:
|
||||
if (paths)
|
||||
usage(builtin_diff_usage);
|
||||
return builtin_diff_blobs(&rev, argc, argv, blob);
|
||||
break;
|
||||
default:
|
||||
usage(builtin_diff_usage);
|
||||
}
|
||||
}
|
||||
else if (blobs)
|
||||
usage(builtin_diff_usage);
|
||||
else if (ents == 1)
|
||||
return builtin_diff_index(&rev, argc, argv);
|
||||
else if (ents == 2)
|
||||
return builtin_diff_tree(&rev, argc, argv, ent);
|
||||
else
|
||||
return builtin_diff_combined(&rev, argc, argv, ent, ents);
|
||||
usage(builtin_diff_usage);
|
||||
}
|
|
@ -3,6 +3,7 @@
|
|||
*
|
||||
* Builtin help-related commands (help, usage, version)
|
||||
*/
|
||||
#include <sys/ioctl.h>
|
||||
#include "cache.h"
|
||||
#include "builtin.h"
|
||||
#include "exec_cmd.h"
|
||||
|
|
|
@ -0,0 +1,312 @@
|
|||
/*
|
||||
* "git push"
|
||||
*/
|
||||
#include "cache.h"
|
||||
#include "refs.h"
|
||||
#include "run-command.h"
|
||||
#include "builtin.h"
|
||||
|
||||
#define MAX_URI (16)
|
||||
|
||||
static const char push_usage[] = "git push [--all] [--tags] [--force] <repository> [<refspec>...]";
|
||||
|
||||
static int all = 0, tags = 0, force = 0, thin = 1;
|
||||
static const char *execute = NULL;
|
||||
|
||||
#define BUF_SIZE (2084)
|
||||
static char buffer[BUF_SIZE];
|
||||
|
||||
static const char **refspec = NULL;
|
||||
static int refspec_nr = 0;
|
||||
|
||||
static void add_refspec(const char *ref)
|
||||
{
|
||||
int nr = refspec_nr + 1;
|
||||
refspec = xrealloc(refspec, nr * sizeof(char *));
|
||||
refspec[nr-1] = ref;
|
||||
refspec_nr = nr;
|
||||
}
|
||||
|
||||
static int expand_one_ref(const char *ref, const unsigned char *sha1)
|
||||
{
|
||||
/* Ignore the "refs/" at the beginning of the refname */
|
||||
ref += 5;
|
||||
|
||||
if (strncmp(ref, "tags/", 5))
|
||||
return 0;
|
||||
|
||||
add_refspec(strdup(ref));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void expand_refspecs(void)
|
||||
{
|
||||
if (all) {
|
||||
if (refspec_nr)
|
||||
die("cannot mix '--all' and a refspec");
|
||||
|
||||
/*
|
||||
* No need to expand "--all" - we'll just use
|
||||
* the "--all" flag to send-pack
|
||||
*/
|
||||
return;
|
||||
}
|
||||
if (!tags)
|
||||
return;
|
||||
for_each_ref(expand_one_ref);
|
||||
}
|
||||
|
||||
static void set_refspecs(const char **refs, int nr)
|
||||
{
|
||||
if (nr) {
|
||||
size_t bytes = nr * sizeof(char *);
|
||||
|
||||
refspec = xrealloc(refspec, bytes);
|
||||
memcpy(refspec, refs, bytes);
|
||||
refspec_nr = nr;
|
||||
}
|
||||
expand_refspecs();
|
||||
}
|
||||
|
||||
static int get_remotes_uri(const char *repo, const char *uri[MAX_URI])
|
||||
{
|
||||
int n = 0;
|
||||
FILE *f = fopen(git_path("remotes/%s", repo), "r");
|
||||
int has_explicit_refspec = refspec_nr || all || tags;
|
||||
|
||||
if (!f)
|
||||
return -1;
|
||||
while (fgets(buffer, BUF_SIZE, f)) {
|
||||
int is_refspec;
|
||||
char *s, *p;
|
||||
|
||||
if (!strncmp("URL: ", buffer, 5)) {
|
||||
is_refspec = 0;
|
||||
s = buffer + 5;
|
||||
} else if (!strncmp("Push: ", buffer, 6)) {
|
||||
is_refspec = 1;
|
||||
s = buffer + 6;
|
||||
} else
|
||||
continue;
|
||||
|
||||
/* Remove whitespace at the head.. */
|
||||
while (isspace(*s))
|
||||
s++;
|
||||
if (!*s)
|
||||
continue;
|
||||
|
||||
/* ..and at the end */
|
||||
p = s + strlen(s);
|
||||
while (isspace(p[-1]))
|
||||
*--p = 0;
|
||||
|
||||
if (!is_refspec) {
|
||||
if (n < MAX_URI)
|
||||
uri[n++] = strdup(s);
|
||||
else
|
||||
error("more than %d URL's specified, ignoreing the rest", MAX_URI);
|
||||
}
|
||||
else if (is_refspec && !has_explicit_refspec)
|
||||
add_refspec(strdup(s));
|
||||
}
|
||||
fclose(f);
|
||||
if (!n)
|
||||
die("remote '%s' has no URL", repo);
|
||||
return n;
|
||||
}
|
||||
|
||||
static const char **config_uri;
|
||||
static const char *config_repo;
|
||||
static int config_repo_len;
|
||||
static int config_current_uri;
|
||||
static int config_get_refspecs;
|
||||
|
||||
static int get_remote_config(const char* key, const char* value)
|
||||
{
|
||||
if (!strncmp(key, "remote.", 7) &&
|
||||
!strncmp(key + 7, config_repo, config_repo_len)) {
|
||||
if (!strcmp(key + 7 + config_repo_len, ".url")) {
|
||||
if (config_current_uri < MAX_URI)
|
||||
config_uri[config_current_uri++] = strdup(value);
|
||||
else
|
||||
error("more than %d URL's specified, ignoring the rest", MAX_URI);
|
||||
}
|
||||
else if (config_get_refspecs &&
|
||||
!strcmp(key + 7 + config_repo_len, ".push"))
|
||||
add_refspec(strdup(value));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int get_config_remotes_uri(const char *repo, const char *uri[MAX_URI])
|
||||
{
|
||||
config_repo_len = strlen(repo);
|
||||
config_repo = repo;
|
||||
config_current_uri = 0;
|
||||
config_uri = uri;
|
||||
config_get_refspecs = !(refspec_nr || all || tags);
|
||||
|
||||
git_config(get_remote_config);
|
||||
return config_current_uri;
|
||||
}
|
||||
|
||||
static int get_branches_uri(const char *repo, const char *uri[MAX_URI])
|
||||
{
|
||||
const char *slash = strchr(repo, '/');
|
||||
int n = slash ? slash - repo : 1000;
|
||||
FILE *f = fopen(git_path("branches/%.*s", n, repo), "r");
|
||||
char *s, *p;
|
||||
int len;
|
||||
|
||||
if (!f)
|
||||
return 0;
|
||||
s = fgets(buffer, BUF_SIZE, f);
|
||||
fclose(f);
|
||||
if (!s)
|
||||
return 0;
|
||||
while (isspace(*s))
|
||||
s++;
|
||||
if (!*s)
|
||||
return 0;
|
||||
p = s + strlen(s);
|
||||
while (isspace(p[-1]))
|
||||
*--p = 0;
|
||||
len = p - s;
|
||||
if (slash)
|
||||
len += strlen(slash);
|
||||
p = xmalloc(len + 1);
|
||||
strcpy(p, s);
|
||||
if (slash)
|
||||
strcat(p, slash);
|
||||
uri[0] = p;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read remotes and branches file, fill the push target URI
|
||||
* list. If there is no command line refspecs, read Push: lines
|
||||
* to set up the *refspec list as well.
|
||||
* return the number of push target URIs
|
||||
*/
|
||||
static int read_config(const char *repo, const char *uri[MAX_URI])
|
||||
{
|
||||
int n;
|
||||
|
||||
if (*repo != '/') {
|
||||
n = get_remotes_uri(repo, uri);
|
||||
if (n > 0)
|
||||
return n;
|
||||
|
||||
n = get_config_remotes_uri(repo, uri);
|
||||
if (n > 0)
|
||||
return n;
|
||||
|
||||
n = get_branches_uri(repo, uri);
|
||||
if (n > 0)
|
||||
return n;
|
||||
}
|
||||
|
||||
uri[0] = repo;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int do_push(const char *repo)
|
||||
{
|
||||
const char *uri[MAX_URI];
|
||||
int i, n;
|
||||
int remote;
|
||||
const char **argv;
|
||||
int argc;
|
||||
|
||||
n = read_config(repo, uri);
|
||||
if (n <= 0)
|
||||
die("bad repository '%s'", repo);
|
||||
|
||||
argv = xmalloc((refspec_nr + 10) * sizeof(char *));
|
||||
argv[0] = "dummy-send-pack";
|
||||
argc = 1;
|
||||
if (all)
|
||||
argv[argc++] = "--all";
|
||||
if (force)
|
||||
argv[argc++] = "--force";
|
||||
if (execute)
|
||||
argv[argc++] = execute;
|
||||
if (thin)
|
||||
argv[argc++] = "--thin";
|
||||
remote = argc;
|
||||
argv[argc++] = "dummy-remote";
|
||||
while (refspec_nr--)
|
||||
argv[argc++] = *refspec++;
|
||||
argv[argc] = NULL;
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
int error;
|
||||
const char *dest = uri[i];
|
||||
const char *sender = "git-send-pack";
|
||||
if (!strncmp(dest, "http://", 7) ||
|
||||
!strncmp(dest, "https://", 8))
|
||||
sender = "git-http-push";
|
||||
argv[0] = sender;
|
||||
argv[remote] = dest;
|
||||
error = run_command_v(argc, argv);
|
||||
if (!error)
|
||||
continue;
|
||||
switch (error) {
|
||||
case -ERR_RUN_COMMAND_FORK:
|
||||
die("unable to fork for %s", sender);
|
||||
case -ERR_RUN_COMMAND_EXEC:
|
||||
die("unable to exec %s", sender);
|
||||
case -ERR_RUN_COMMAND_WAITPID:
|
||||
case -ERR_RUN_COMMAND_WAITPID_WRONG_PID:
|
||||
case -ERR_RUN_COMMAND_WAITPID_SIGNAL:
|
||||
case -ERR_RUN_COMMAND_WAITPID_NOEXIT:
|
||||
die("%s died with strange error", sender);
|
||||
default:
|
||||
return -error;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cmd_push(int argc, const char **argv, char **envp)
|
||||
{
|
||||
int i;
|
||||
const char *repo = "origin"; // default repository
|
||||
|
||||
for (i = 1; i < argc; i++) {
|
||||
const char *arg = argv[i];
|
||||
|
||||
if (arg[0] != '-') {
|
||||
repo = arg;
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
if (!strcmp(arg, "--all")) {
|
||||
all = 1;
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(arg, "--tags")) {
|
||||
tags = 1;
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(arg, "--force")) {
|
||||
force = 1;
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(arg, "--thin")) {
|
||||
thin = 1;
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(arg, "--no-thin")) {
|
||||
thin = 0;
|
||||
continue;
|
||||
}
|
||||
if (!strncmp(arg, "--exec=", 7)) {
|
||||
execute = arg;
|
||||
continue;
|
||||
}
|
||||
usage(push_usage);
|
||||
}
|
||||
set_refspecs(argv + i, argc - i);
|
||||
return do_push(repo);
|
||||
}
|
|
@ -19,6 +19,10 @@ extern int cmd_version(int argc, const char **argv, char **envp);
|
|||
extern int cmd_whatchanged(int argc, const char **argv, char **envp);
|
||||
extern int cmd_show(int argc, const char **argv, char **envp);
|
||||
extern int cmd_log(int argc, const char **argv, char **envp);
|
||||
extern int cmd_diff(int argc, const char **argv, char **envp);
|
||||
extern int cmd_format_patch(int argc, const char **argv, char **envp);
|
||||
extern int cmd_count_objects(int argc, const char **argv, char **envp);
|
||||
|
||||
extern int cmd_push(int argc, const char **argv, char **envp);
|
||||
|
||||
#endif
|
||||
|
|
4
cache.h
4
cache.h
|
@ -134,6 +134,8 @@ extern const char *setup_git_directory_gently(int *);
|
|||
extern const char *setup_git_directory(void);
|
||||
extern const char *prefix_path(const char *prefix, int len, const char *path);
|
||||
extern const char *prefix_filename(const char *prefix, int len, const char *path);
|
||||
extern void verify_filename(const char *prefix, const char *name);
|
||||
extern void verify_non_filename(const char *prefix, const char *name);
|
||||
|
||||
#define alloc_nr(x) (((x)+16)*3/2)
|
||||
|
||||
|
@ -167,7 +169,7 @@ extern void rollback_index_file(struct cache_file *);
|
|||
/* Environment bits from configuration mechanism */
|
||||
extern int trust_executable_bit;
|
||||
extern int assume_unchanged;
|
||||
extern int only_use_symrefs;
|
||||
extern int prefer_symlink_refs;
|
||||
extern int warn_ambiguous_refs;
|
||||
extern int diff_rename_limit_default;
|
||||
extern int shared_repository;
|
||||
|
|
|
@ -269,12 +269,16 @@ int main(int argc, char **argv)
|
|||
/* Check out named files first */
|
||||
for ( ; i < argc; i++) {
|
||||
const char *arg = argv[i];
|
||||
const char *p;
|
||||
|
||||
if (all)
|
||||
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");
|
||||
checkout_file(prefix_path(prefix, prefix_length, arg));
|
||||
p = prefix_path(prefix, prefix_length, arg);
|
||||
checkout_file(p);
|
||||
if (p != arg)
|
||||
free((char*)p);
|
||||
}
|
||||
|
||||
if (read_from_stdin) {
|
||||
|
@ -284,6 +288,8 @@ int main(int argc, char **argv)
|
|||
strbuf_init(&buf);
|
||||
while (1) {
|
||||
char *path_name;
|
||||
const char *p;
|
||||
|
||||
read_line(&buf, stdin, line_termination);
|
||||
if (buf.eof)
|
||||
break;
|
||||
|
@ -291,7 +297,10 @@ int main(int argc, char **argv)
|
|||
path_name = unquote_c_style(buf.buf, NULL);
|
||||
else
|
||||
path_name = buf.buf;
|
||||
checkout_file(prefix_path(prefix, prefix_length, path_name));
|
||||
p = prefix_path(prefix, prefix_length, path_name);
|
||||
checkout_file(p);
|
||||
if (p != path_name)
|
||||
free((char *)p);
|
||||
if (path_name != buf.buf)
|
||||
free(path_name);
|
||||
}
|
||||
|
|
|
@ -831,15 +831,16 @@ void show_combined_diff(struct combine_diff_path *p,
|
|||
}
|
||||
}
|
||||
|
||||
void diff_tree_combined_merge(const unsigned char *sha1,
|
||||
int dense, struct rev_info *rev)
|
||||
void diff_tree_combined(const unsigned char *sha1,
|
||||
const unsigned char parent[][20],
|
||||
int num_parent,
|
||||
int dense,
|
||||
struct rev_info *rev)
|
||||
{
|
||||
struct diff_options *opt = &rev->diffopt;
|
||||
struct commit *commit = lookup_commit(sha1);
|
||||
struct diff_options diffopts;
|
||||
struct commit_list *parents;
|
||||
struct combine_diff_path *p, *paths = NULL;
|
||||
int num_parent, i, num_paths;
|
||||
int i, num_paths;
|
||||
int do_diffstat;
|
||||
|
||||
do_diffstat = (opt->output_format == DIFF_FORMAT_DIFFSTAT ||
|
||||
|
@ -849,17 +850,8 @@ void diff_tree_combined_merge(const unsigned char *sha1,
|
|||
diffopts.with_stat = 0;
|
||||
diffopts.recursive = 1;
|
||||
|
||||
/* count parents */
|
||||
for (parents = commit->parents, num_parent = 0;
|
||||
parents;
|
||||
parents = parents->next, num_parent++)
|
||||
; /* nothing */
|
||||
|
||||
/* find set of paths that everybody touches */
|
||||
for (parents = commit->parents, i = 0;
|
||||
parents;
|
||||
parents = parents->next, i++) {
|
||||
struct commit *parent = parents->item;
|
||||
for (i = 0; i < num_parent; i++) {
|
||||
/* show stat against the first parent even
|
||||
* when doing combined diff.
|
||||
*/
|
||||
|
@ -867,8 +859,7 @@ void diff_tree_combined_merge(const unsigned char *sha1,
|
|||
diffopts.output_format = DIFF_FORMAT_DIFFSTAT;
|
||||
else
|
||||
diffopts.output_format = DIFF_FORMAT_NO_OUTPUT;
|
||||
diff_tree_sha1(parent->object.sha1, commit->object.sha1, "",
|
||||
&diffopts);
|
||||
diff_tree_sha1(parent[i], sha1, "", &diffopts);
|
||||
diffcore_std(&diffopts);
|
||||
paths = intersect_paths(paths, i, num_parent);
|
||||
|
||||
|
@ -907,3 +898,25 @@ void diff_tree_combined_merge(const unsigned char *sha1,
|
|||
free(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
void diff_tree_combined_merge(const unsigned char *sha1,
|
||||
int dense, struct rev_info *rev)
|
||||
{
|
||||
int num_parent;
|
||||
const unsigned char (*parent)[20];
|
||||
struct commit *commit = lookup_commit(sha1);
|
||||
struct commit_list *parents;
|
||||
|
||||
/* count parents */
|
||||
for (parents = commit->parents, num_parent = 0;
|
||||
parents;
|
||||
parents = parents->next, num_parent++)
|
||||
; /* nothing */
|
||||
|
||||
parent = xmalloc(num_parent * sizeof(*parent));
|
||||
for (parents = commit->parents, num_parent = 0;
|
||||
parents;
|
||||
parents = parents->next, num_parent++)
|
||||
memcpy(parent + num_parent, parents->item->object.sha1, 20);
|
||||
diff_tree_combined(sha1, parent, num_parent, dense, rev);
|
||||
}
|
||||
|
|
|
@ -45,14 +45,13 @@ static void add_buffer(char **bufp, unsigned int *sizep, const char *fmt, ...)
|
|||
|
||||
static void check_valid(unsigned char *sha1, const char *expect)
|
||||
{
|
||||
void *buf;
|
||||
char type[20];
|
||||
unsigned long size;
|
||||
|
||||
buf = read_sha1_file(sha1, type, &size);
|
||||
if (!buf || strcmp(type, expect))
|
||||
die("%s is not a valid '%s' object", sha1_to_hex(sha1), expect);
|
||||
free(buf);
|
||||
if (sha1_object_info(sha1, type, NULL))
|
||||
die("%s is not a valid object", sha1_to_hex(sha1));
|
||||
if (expect && strcmp(type, expect))
|
||||
die("%s is not a valid '%s' object", sha1_to_hex(sha1),
|
||||
expect);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -92,7 +91,7 @@ int main(int argc, char **argv)
|
|||
|
||||
git_config(git_default_config);
|
||||
|
||||
if (argc < 2 || get_sha1_hex(argv[1], tree_sha1) < 0)
|
||||
if (argc < 2 || get_sha1(argv[1], tree_sha1) < 0)
|
||||
usage(commit_tree_usage);
|
||||
|
||||
check_valid(tree_sha1, tree_type);
|
||||
|
|
25
config.c
25
config.c
|
@ -60,6 +60,12 @@ static char *parse_value(void)
|
|||
space = 1;
|
||||
continue;
|
||||
}
|
||||
if (!quote) {
|
||||
if (c == ';' || c == '#') {
|
||||
comment = 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (space) {
|
||||
if (len)
|
||||
value[len++] = ' ';
|
||||
|
@ -93,12 +99,6 @@ static char *parse_value(void)
|
|||
quote = 1-quote;
|
||||
continue;
|
||||
}
|
||||
if (!quote) {
|
||||
if (c == ';' || c == '#') {
|
||||
comment = 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
value[len++] = c;
|
||||
}
|
||||
}
|
||||
|
@ -227,8 +227,8 @@ int git_default_config(const char *var, const char *value)
|
|||
return 0;
|
||||
}
|
||||
|
||||
if (!strcmp(var, "core.symrefsonly")) {
|
||||
only_use_symrefs = git_config_bool(var, value);
|
||||
if (!strcmp(var, "core.prefersymlinkrefs")) {
|
||||
prefer_symlink_refs = git_config_bool(var, value);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -252,7 +252,7 @@ int git_default_config(const char *var, const char *value)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* Add other config variables here.. */
|
||||
/* Add other config variables here and to Documentation/config.txt. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -335,8 +335,11 @@ static int store_aux(const char* key, const char* value)
|
|||
store.offset[store.seen] = ftell(config_file);
|
||||
store.state = KEY_SEEN;
|
||||
store.seen++;
|
||||
} else if(!strncmp(key, store.key, store.baselen))
|
||||
store.state = SECTION_SEEN;
|
||||
} else if (strrchr(key, '.') - key == store.baselen &&
|
||||
!strncmp(key, store.key, store.baselen)) {
|
||||
store.state = SECTION_SEEN;
|
||||
store.offset[store.seen] = ftell(config_file);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
This is "colordiff" (http://colordiff.sourceforge.net/) by Dave
|
||||
Ewart <davee@sungate.co.uk>, modified specifically for git.
|
|
@ -0,0 +1,196 @@
|
|||
#!/usr/bin/perl -w
|
||||
#
|
||||
# $Id: colordiff.pl,v 1.4.2.10 2004/01/04 15:02:59 daveewart Exp $
|
||||
|
||||
########################################################################
|
||||
# #
|
||||
# ColorDiff - a wrapper/replacment for 'diff' producing #
|
||||
# colourful output #
|
||||
# #
|
||||
# Copyright (C)2002-2004 Dave Ewart (davee@sungate.co.uk) #
|
||||
# #
|
||||
########################################################################
|
||||
# #
|
||||
# This program is free software; you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation; either version 2 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program; if not, write to the Free Software #
|
||||
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. #
|
||||
# #
|
||||
########################################################################
|
||||
|
||||
use strict;
|
||||
use Getopt::Long qw(:config pass_through);
|
||||
use IPC::Open2;
|
||||
|
||||
my $app_name = 'colordiff';
|
||||
my $version = '1.0.4';
|
||||
my $author = 'Dave Ewart';
|
||||
my $author_email = 'davee@sungate.co.uk';
|
||||
my $app_www = 'http://colordiff.sourceforge.net/';
|
||||
my $copyright = '(C)2002-2004';
|
||||
my $show_banner = 1;
|
||||
|
||||
# ANSI sequences for colours
|
||||
my %colour;
|
||||
$colour{white} = "\033[1;37m";
|
||||
$colour{yellow} = "\033[1;33m";
|
||||
$colour{green} = "\033[1;32m";
|
||||
$colour{blue} = "\033[1;34m";
|
||||
$colour{cyan} = "\033[1;36m";
|
||||
$colour{red} = "\033[1;31m";
|
||||
$colour{magenta} = "\033[1;35m";
|
||||
$colour{black} = "\033[1;30m";
|
||||
$colour{darkwhite} = "\033[0;37m";
|
||||
$colour{darkyellow} = "\033[0;33m";
|
||||
$colour{darkgreen} = "\033[0;32m";
|
||||
$colour{darkblue} = "\033[0;34m";
|
||||
$colour{darkcyan} = "\033[0;36m";
|
||||
$colour{darkred} = "\033[0;31m";
|
||||
$colour{darkmagenta} = "\033[0;35m";
|
||||
$colour{darkblack} = "\033[0;30m";
|
||||
$colour{OFF} = "\033[0;0m";
|
||||
|
||||
# Default colours if /etc/colordiffrc or ~/.colordiffrc do not exist
|
||||
my $plain_text = $colour{OFF};
|
||||
my $file_old = $colour{red};
|
||||
my $file_new = $colour{blue};
|
||||
my $diff_stuff = $colour{magenta};
|
||||
|
||||
# Locations for personal and system-wide colour configurations
|
||||
my $HOME = $ENV{HOME};
|
||||
my $etcdir = '/etc';
|
||||
|
||||
my ($setting, $value);
|
||||
my @config_files = ("$etcdir/colordiffrc", "$HOME/.colordiffrc");
|
||||
my $config_file;
|
||||
|
||||
foreach $config_file (@config_files) {
|
||||
if (open(COLORDIFFRC, "<$config_file")) {
|
||||
while (<COLORDIFFRC>) {
|
||||
chop;
|
||||
next if (/^#/ || /^$/);
|
||||
s/\s+//g;
|
||||
($setting, $value) = split ('=');
|
||||
if ($setting eq 'banner') {
|
||||
if ($value eq 'no') {
|
||||
$show_banner = 0;
|
||||
}
|
||||
next;
|
||||
}
|
||||
if (!defined $colour{$value}) {
|
||||
print "Invalid colour specification ($value) in $config_file\n";
|
||||
next;
|
||||
}
|
||||
if ($setting eq 'plain') {
|
||||
$plain_text = $colour{$value};
|
||||
}
|
||||
elsif ($setting eq 'oldtext') {
|
||||
$file_old = $colour{$value};
|
||||
}
|
||||
elsif ($setting eq 'newtext') {
|
||||
$file_new = $colour{$value};
|
||||
}
|
||||
elsif ($setting eq 'diffstuff') {
|
||||
$diff_stuff = $colour{$value};
|
||||
}
|
||||
else {
|
||||
print "Unknown option in $etcdir/colordiffrc: $setting\n";
|
||||
}
|
||||
}
|
||||
close COLORDIFFRC;
|
||||
}
|
||||
}
|
||||
|
||||
# colordiff specfic options here. Need to pre-declare if using variables
|
||||
GetOptions(
|
||||
"no-banner" => sub { $show_banner = 0 },
|
||||
"plain-text=s" => \&set_color,
|
||||
"file-old=s" => \&set_color,
|
||||
"file-new=s" => \&set_color,
|
||||
"diff-stuff=s" => \&set_color
|
||||
);
|
||||
|
||||
if ($show_banner == 1) {
|
||||
print STDERR "$app_name $version ($app_www)\n";
|
||||
print STDERR "$copyright $author, $author_email\n\n";
|
||||
}
|
||||
|
||||
if (defined $ARGV[0]) {
|
||||
# More reliable way of pulling in arguments
|
||||
open2(\*INPUTSTREAM, undef, "git", "diff", @ARGV);
|
||||
}
|
||||
else {
|
||||
*INPUTSTREAM = \*STDIN;
|
||||
}
|
||||
|
||||
my $record;
|
||||
my $nrecs = 0;
|
||||
my $inside_file_old = 1;
|
||||
my $nparents = undef;
|
||||
|
||||
while (<INPUTSTREAM>) {
|
||||
$nrecs++;
|
||||
if (/^(\@\@+) -[-+0-9, ]+ \1/) {
|
||||
print "$diff_stuff";
|
||||
$nparents = length($1) - 1;
|
||||
}
|
||||
elsif (/^diff -/ || /^index / ||
|
||||
/^old mode / || /^new mode / ||
|
||||
/^deleted file mode / || /^new file mode / ||
|
||||
/^similarity index / || /^dissimilarity index / ||
|
||||
/^copy from / || /^copy to / ||
|
||||
/^rename from / || /^rename to /) {
|
||||
$nparents = undef;
|
||||
print "$diff_stuff";
|
||||
}
|
||||
elsif (defined $nparents) {
|
||||
if ($nparents == 1) {
|
||||
if (/^\+/) {
|
||||
print $file_new;
|
||||
}
|
||||
elsif (/^-/) {
|
||||
print $file_old;
|
||||
}
|
||||
else {
|
||||
print $plain_text;
|
||||
}
|
||||
}
|
||||
elsif (/^ {$nparents}/) {
|
||||
print "$plain_text";
|
||||
}
|
||||
elsif (/^[+ ]{$nparents}/) {
|
||||
print "$file_new";
|
||||
}
|
||||
elsif (/^[- ]{$nparents}/) {
|
||||
print "$file_old";
|
||||
}
|
||||
else {
|
||||
print $plain_text;
|
||||
}
|
||||
}
|
||||
elsif (/^--- / || /^\+\+\+ /) {
|
||||
print $diff_stuff;
|
||||
}
|
||||
else {
|
||||
print "$plain_text";
|
||||
}
|
||||
s/$/$colour{OFF}/;
|
||||
print "$_";
|
||||
}
|
||||
close INPUTSTREAM;
|
||||
|
||||
sub set_color {
|
||||
my ($type, $color) = @_;
|
||||
|
||||
$type =~ s/-/_/;
|
||||
eval "\$$type = \$colour{$color}";
|
||||
}
|
|
@ -8,7 +8,7 @@
|
|||
$GIT_SVN_INDEX $GIT_SVN
|
||||
$GIT_DIR $REV_DIR/;
|
||||
$AUTHOR = 'Eric Wong <normalperson@yhbt.net>';
|
||||
$VERSION = '0.11.0';
|
||||
$VERSION = '1.0.0';
|
||||
|
||||
use Cwd qw/abs_path/;
|
||||
$GIT_DIR = abs_path($ENV{GIT_DIR} || '.git');
|
||||
|
@ -42,7 +42,8 @@
|
|||
my %cmd = (
|
||||
fetch => [ \&fetch, "Download new revisions from SVN",
|
||||
{ 'revision|r=s' => \$_revision, %fc_opts } ],
|
||||
init => [ \&init, "Initialize and fetch (import)", { } ],
|
||||
init => [ \&init, "Initialize a repo for tracking" .
|
||||
" (requires URL argument)", { } ],
|
||||
commit => [ \&commit, "Commit git revisions to SVN",
|
||||
{ 'stdin|' => \$_stdin,
|
||||
'edit|e' => \$_edit,
|
||||
|
@ -220,7 +221,8 @@ sub rebuild {
|
|||
}
|
||||
|
||||
sub init {
|
||||
$SVN_URL = shift or croak "SVN repository location required\n";
|
||||
$SVN_URL = shift or die "SVN repository location required " .
|
||||
"as a command-line argument\n";
|
||||
unless (-d $GIT_DIR) {
|
||||
sys('git-init-db');
|
||||
}
|
||||
|
|
|
@ -36,17 +36,22 @@ COMMANDS
|
|||
--------
|
||||
init::
|
||||
Creates an empty git repository with additional metadata
|
||||
directories for git-svn. The SVN_URL must be specified
|
||||
at this point.
|
||||
directories for git-svn. The Subversion URL must be specified
|
||||
as a command-line argument.
|
||||
|
||||
fetch::
|
||||
Fetch unfetched revisions from the SVN_URL we are tracking.
|
||||
refs/heads/remotes/git-svn will be updated to the latest revision.
|
||||
Fetch unfetched revisions from the Subversion URL we are
|
||||
tracking. refs/remotes/git-svn will be updated to the
|
||||
latest revision.
|
||||
|
||||
Note: You should never attempt to modify the remotes/git-svn branch
|
||||
outside of git-svn. Instead, create a branch from remotes/git-svn
|
||||
and work on that branch. Use the 'commit' command (see below)
|
||||
to write git commits back to remotes/git-svn.
|
||||
Note: You should never attempt to modify the remotes/git-svn
|
||||
branch outside of git-svn. Instead, create a branch from
|
||||
remotes/git-svn and work on that branch. Use the 'commit'
|
||||
command (see below) to write git commits back to
|
||||
remotes/git-svn.
|
||||
|
||||
See 'Additional Fetch Arguments' if you are interested in
|
||||
manually joining branches on commit.
|
||||
|
||||
commit::
|
||||
Commit specified commit or tree objects to SVN. This relies on
|
||||
|
@ -62,9 +67,9 @@ rebuild::
|
|||
tracked with git-svn. Unfortunately, git-clone does not clone
|
||||
git-svn metadata and the svn working tree that git-svn uses for
|
||||
its operations. This rebuilds the metadata so git-svn can
|
||||
resume fetch operations. SVN_URL may be optionally specified if
|
||||
the directory/repository you're tracking has moved or changed
|
||||
protocols.
|
||||
resume fetch operations. A Subversion URL may be optionally
|
||||
specified at the command-line if the directory/repository you're
|
||||
tracking has moved or changed protocols.
|
||||
|
||||
show-ignore::
|
||||
Recursively finds and lists the svn:ignore property on
|
||||
|
@ -123,6 +128,24 @@ OPTIONS
|
|||
repo-config key: svn.l
|
||||
repo-config key: svn.findcopiesharder
|
||||
|
||||
-A<filename>::
|
||||
--authors-file=<filename>::
|
||||
|
||||
Syntax is compatible with the files used by git-svnimport and
|
||||
git-cvsimport:
|
||||
|
||||
------------------------------------------------------------------------
|
||||
loginname = Joe User <user@example.com>
|
||||
------------------------------------------------------------------------
|
||||
|
||||
If this option is specified and git-svn encounters an SVN
|
||||
committer name that does not exist in the authors-file, git-svn
|
||||
will abort operation. The user will then have to add the
|
||||
appropriate entry. Re-running the previous git-svn command
|
||||
after the authors-file is modified should continue operation.
|
||||
|
||||
repo-config key: svn.authors-file
|
||||
|
||||
ADVANCED OPTIONS
|
||||
----------------
|
||||
-b<refname>::
|
||||
|
|
2
daemon.c
2
daemon.c
|
@ -535,7 +535,7 @@ static int socksetup(int port, int **socklist_p)
|
|||
|
||||
if (set_reuse_addr(sockfd)) {
|
||||
close(sockfd);
|
||||
return 0; /* not fatal */
|
||||
continue;
|
||||
}
|
||||
|
||||
if (bind(sockfd, ai->ai_addr, ai->ai_addrlen) < 0) {
|
||||
|
|
6
delta.h
6
delta.h
|
@ -2,11 +2,11 @@
|
|||
#define DELTA_H
|
||||
|
||||
/* handling of delta buffers */
|
||||
extern void *diff_delta(void *from_buf, unsigned long from_size,
|
||||
void *to_buf, unsigned long to_size,
|
||||
extern void *diff_delta(const void *from_buf, unsigned long from_size,
|
||||
const void *to_buf, unsigned long to_size,
|
||||
unsigned long *delta_size, unsigned long max_size);
|
||||
extern void *patch_delta(void *src_buf, unsigned long src_size,
|
||||
void *delta_buf, unsigned long delta_size,
|
||||
const void *delta_buf, unsigned long delta_size,
|
||||
unsigned long *dst_size);
|
||||
|
||||
/* the smallest possible delta size is 4 bytes */
|
||||
|
|
|
@ -131,8 +131,8 @@ static struct index ** delta_index(const unsigned char *buf,
|
|||
/* the maximum size for any opcode */
|
||||
#define MAX_OP_SIZE COPYOP_SIZE(0xffffffff, 0xffffffff)
|
||||
|
||||
void *diff_delta(void *from_buf, unsigned long from_size,
|
||||
void *to_buf, unsigned long to_size,
|
||||
void *diff_delta(const void *from_buf, unsigned long from_size,
|
||||
const void *to_buf, unsigned long to_size,
|
||||
unsigned long *delta_size,
|
||||
unsigned long max_size)
|
||||
{
|
||||
|
|
214
diff-files.c
214
diff-files.c
|
@ -12,203 +12,43 @@ static const char diff_files_usage[] =
|
|||
"git-diff-files [-q] [-0/-1/2/3 |-c|--cc] [<common diff options>] [<path>...]"
|
||||
COMMON_DIFF_OPTIONS_HELP;
|
||||
|
||||
static struct rev_info rev;
|
||||
static int silent = 0;
|
||||
static int diff_unmerged_stage = 2;
|
||||
static int combine_merges = 0;
|
||||
static int dense_combined_merges = 0;
|
||||
|
||||
static void show_unmerge(const char *path)
|
||||
{
|
||||
diff_unmerge(&rev.diffopt, path);
|
||||
}
|
||||
|
||||
static void show_file(int pfx, struct cache_entry *ce)
|
||||
{
|
||||
diff_addremove(&rev.diffopt, pfx, ntohl(ce->ce_mode),
|
||||
ce->sha1, ce->name, NULL);
|
||||
}
|
||||
|
||||
static void show_modified(int oldmode, int mode,
|
||||
const unsigned char *old_sha1, const unsigned char *sha1,
|
||||
char *path)
|
||||
{
|
||||
diff_change(&rev.diffopt, oldmode, mode, old_sha1, sha1, path, NULL);
|
||||
}
|
||||
|
||||
int main(int argc, const char **argv)
|
||||
{
|
||||
const char **pathspec;
|
||||
const char *prefix = setup_git_directory();
|
||||
int entries, i;
|
||||
struct rev_info rev;
|
||||
int silent = 0;
|
||||
|
||||
git_config(git_diff_config);
|
||||
diff_setup(&rev.diffopt);
|
||||
init_revisions(&rev);
|
||||
rev.abbrev = 0;
|
||||
|
||||
argc = setup_revisions(argc, argv, &rev, NULL);
|
||||
while (1 < argc && argv[1][0] == '-') {
|
||||
if (!strcmp(argv[1], "--")) {
|
||||
argv++;
|
||||
argc--;
|
||||
break;
|
||||
}
|
||||
if (!strcmp(argv[1], "-0"))
|
||||
diff_unmerged_stage = 0;
|
||||
else if (!strcmp(argv[1], "-1"))
|
||||
diff_unmerged_stage = 1;
|
||||
else if (!strcmp(argv[1], "-2"))
|
||||
diff_unmerged_stage = 2;
|
||||
else if (!strcmp(argv[1], "-3"))
|
||||
diff_unmerged_stage = 3;
|
||||
else if (!strcmp(argv[1], "--base"))
|
||||
diff_unmerged_stage = 1;
|
||||
if (!strcmp(argv[1], "--base"))
|
||||
rev.max_count = 1;
|
||||
else if (!strcmp(argv[1], "--ours"))
|
||||
diff_unmerged_stage = 2;
|
||||
rev.max_count = 2;
|
||||
else if (!strcmp(argv[1], "--theirs"))
|
||||
diff_unmerged_stage = 3;
|
||||
rev.max_count = 3;
|
||||
else if (!strcmp(argv[1], "-q"))
|
||||
silent = 1;
|
||||
else if (!strcmp(argv[1], "-r"))
|
||||
; /* no-op */
|
||||
else if (!strcmp(argv[1], "-s"))
|
||||
; /* no-op */
|
||||
else if (!strcmp(argv[1], "-c"))
|
||||
combine_merges = 1;
|
||||
else if (!strcmp(argv[1], "--cc"))
|
||||
dense_combined_merges = combine_merges = 1;
|
||||
else {
|
||||
int diff_opt_cnt;
|
||||
diff_opt_cnt = diff_opt_parse(&rev.diffopt,
|
||||
argv+1, argc-1);
|
||||
if (diff_opt_cnt < 0)
|
||||
usage(diff_files_usage);
|
||||
else if (diff_opt_cnt) {
|
||||
argv += diff_opt_cnt;
|
||||
argc -= diff_opt_cnt;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
usage(diff_files_usage);
|
||||
}
|
||||
else
|
||||
usage(diff_files_usage);
|
||||
argv++; argc--;
|
||||
}
|
||||
if (dense_combined_merges)
|
||||
rev.diffopt.output_format = DIFF_FORMAT_PATCH;
|
||||
|
||||
/* Find the directory, and set up the pathspec */
|
||||
pathspec = get_pathspec(prefix, argv + 1);
|
||||
entries = read_cache();
|
||||
|
||||
if (diff_setup_done(&rev.diffopt) < 0)
|
||||
usage(diff_files_usage);
|
||||
|
||||
/* At this point, if argc == 1, then we are doing everything.
|
||||
* Otherwise argv[1] .. argv[argc-1] have the explicit paths.
|
||||
/*
|
||||
* Make sure there are NO revision (i.e. pending object) parameter,
|
||||
* rev.max_count is reasonable (0 <= n <= 3),
|
||||
* there is no other revision filtering parameters.
|
||||
*/
|
||||
if (entries < 0) {
|
||||
perror("read_cache");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
for (i = 0; i < entries; i++) {
|
||||
struct stat st;
|
||||
unsigned int oldmode, newmode;
|
||||
struct cache_entry *ce = active_cache[i];
|
||||
int changed;
|
||||
|
||||
if (!ce_path_match(ce, pathspec))
|
||||
continue;
|
||||
|
||||
if (ce_stage(ce)) {
|
||||
struct {
|
||||
struct combine_diff_path p;
|
||||
struct combine_diff_parent filler[5];
|
||||
} combine;
|
||||
int num_compare_stages = 0;
|
||||
|
||||
combine.p.next = NULL;
|
||||
combine.p.len = ce_namelen(ce);
|
||||
combine.p.path = xmalloc(combine.p.len + 1);
|
||||
memcpy(combine.p.path, ce->name, combine.p.len);
|
||||
combine.p.path[combine.p.len] = 0;
|
||||
combine.p.mode = 0;
|
||||
memset(combine.p.sha1, 0, 20);
|
||||
memset(&combine.p.parent[0], 0,
|
||||
sizeof(combine.filler));
|
||||
|
||||
while (i < entries) {
|
||||
struct cache_entry *nce = active_cache[i];
|
||||
int stage;
|
||||
|
||||
if (strcmp(ce->name, nce->name))
|
||||
break;
|
||||
|
||||
/* Stage #2 (ours) is the first parent,
|
||||
* stage #3 (theirs) is the second.
|
||||
*/
|
||||
stage = ce_stage(nce);
|
||||
if (2 <= stage) {
|
||||
int mode = ntohl(nce->ce_mode);
|
||||
num_compare_stages++;
|
||||
memcpy(combine.p.parent[stage-2].sha1,
|
||||
nce->sha1, 20);
|
||||
combine.p.parent[stage-2].mode =
|
||||
canon_mode(mode);
|
||||
combine.p.parent[stage-2].status =
|
||||
DIFF_STATUS_MODIFIED;
|
||||
}
|
||||
|
||||
/* diff against the proper unmerged stage */
|
||||
if (stage == diff_unmerged_stage)
|
||||
ce = nce;
|
||||
i++;
|
||||
}
|
||||
/*
|
||||
* Compensate for loop update
|
||||
*/
|
||||
i--;
|
||||
|
||||
if (combine_merges && num_compare_stages == 2) {
|
||||
show_combined_diff(&combine.p, 2,
|
||||
dense_combined_merges,
|
||||
&rev);
|
||||
free(combine.p.path);
|
||||
continue;
|
||||
}
|
||||
free(combine.p.path);
|
||||
|
||||
/*
|
||||
* Show the diff for the 'ce' if we found the one
|
||||
* from the desired stage.
|
||||
*/
|
||||
show_unmerge(ce->name);
|
||||
if (ce_stage(ce) != diff_unmerged_stage)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (lstat(ce->name, &st) < 0) {
|
||||
if (errno != ENOENT && errno != ENOTDIR) {
|
||||
perror(ce->name);
|
||||
continue;
|
||||
}
|
||||
if (silent)
|
||||
continue;
|
||||
show_file('-', ce);
|
||||
continue;
|
||||
}
|
||||
changed = ce_match_stat(ce, &st, 0);
|
||||
if (!changed && !rev.diffopt.find_copies_harder)
|
||||
continue;
|
||||
oldmode = ntohl(ce->ce_mode);
|
||||
|
||||
newmode = canon_mode(st.st_mode);
|
||||
if (!trust_executable_bit &&
|
||||
S_ISREG(newmode) && S_ISREG(oldmode) &&
|
||||
((newmode ^ oldmode) == 0111))
|
||||
newmode = oldmode;
|
||||
show_modified(oldmode, newmode,
|
||||
ce->sha1, (changed ? null_sha1 : ce->sha1),
|
||||
ce->name);
|
||||
}
|
||||
diffcore_std(&rev.diffopt);
|
||||
diff_flush(&rev.diffopt);
|
||||
return 0;
|
||||
if (rev.pending_objects ||
|
||||
rev.min_age != -1 || rev.max_age != -1)
|
||||
usage(diff_files_usage);
|
||||
/*
|
||||
* Backward compatibility wart - "diff-files -s" used to
|
||||
* defeat the common diff option "-s" which asked for
|
||||
* DIFF_FORMAT_NO_OUTPUT.
|
||||
*/
|
||||
if (rev.diffopt.output_format == DIFF_FORMAT_NO_OUTPUT)
|
||||
rev.diffopt.output_format = DIFF_FORMAT_RAW;
|
||||
return run_diff_files(&rev, silent);
|
||||
}
|
||||
|
|
251
diff-index.c
251
diff-index.c
|
@ -1,166 +1,7 @@
|
|||
#include "cache.h"
|
||||
#include "tree.h"
|
||||
#include "diff.h"
|
||||
|
||||
static int cached_only = 0;
|
||||
static int match_nonexisting = 0;
|
||||
static struct diff_options diff_options;
|
||||
|
||||
/* A file entry went away or appeared */
|
||||
static void show_file(const char *prefix,
|
||||
struct cache_entry *ce,
|
||||
unsigned char *sha1, unsigned int mode)
|
||||
{
|
||||
diff_addremove(&diff_options, prefix[0], ntohl(mode),
|
||||
sha1, ce->name, NULL);
|
||||
}
|
||||
|
||||
static int get_stat_data(struct cache_entry *ce,
|
||||
unsigned char ** sha1p, unsigned int *modep)
|
||||
{
|
||||
unsigned char *sha1 = ce->sha1;
|
||||
unsigned int mode = ce->ce_mode;
|
||||
|
||||
if (!cached_only) {
|
||||
static unsigned char no_sha1[20];
|
||||
int changed;
|
||||
struct stat st;
|
||||
if (lstat(ce->name, &st) < 0) {
|
||||
if (errno == ENOENT && match_nonexisting) {
|
||||
*sha1p = sha1;
|
||||
*modep = mode;
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
changed = ce_match_stat(ce, &st, 0);
|
||||
if (changed) {
|
||||
mode = create_ce_mode(st.st_mode);
|
||||
if (!trust_executable_bit && S_ISREG(st.st_mode))
|
||||
mode = ce->ce_mode;
|
||||
sha1 = no_sha1;
|
||||
}
|
||||
}
|
||||
|
||||
*sha1p = sha1;
|
||||
*modep = mode;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void show_new_file(struct cache_entry *new)
|
||||
{
|
||||
unsigned char *sha1;
|
||||
unsigned int mode;
|
||||
|
||||
/* New file in the index: it might actually be different in
|
||||
* the working copy.
|
||||
*/
|
||||
if (get_stat_data(new, &sha1, &mode) < 0)
|
||||
return;
|
||||
|
||||
show_file("+", new, sha1, mode);
|
||||
}
|
||||
|
||||
static int show_modified(struct cache_entry *old,
|
||||
struct cache_entry *new,
|
||||
int report_missing)
|
||||
{
|
||||
unsigned int mode, oldmode;
|
||||
unsigned char *sha1;
|
||||
|
||||
if (get_stat_data(new, &sha1, &mode) < 0) {
|
||||
if (report_missing)
|
||||
show_file("-", old, old->sha1, old->ce_mode);
|
||||
return -1;
|
||||
}
|
||||
|
||||
oldmode = old->ce_mode;
|
||||
if (mode == oldmode && !memcmp(sha1, old->sha1, 20) &&
|
||||
!diff_options.find_copies_harder)
|
||||
return 0;
|
||||
|
||||
mode = ntohl(mode);
|
||||
oldmode = ntohl(oldmode);
|
||||
|
||||
diff_change(&diff_options, oldmode, mode,
|
||||
old->sha1, sha1, old->name, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int diff_cache(struct cache_entry **ac, int entries, const char **pathspec)
|
||||
{
|
||||
while (entries) {
|
||||
struct cache_entry *ce = *ac;
|
||||
int same = (entries > 1) && ce_same_name(ce, ac[1]);
|
||||
|
||||
if (!ce_path_match(ce, pathspec))
|
||||
goto skip_entry;
|
||||
|
||||
switch (ce_stage(ce)) {
|
||||
case 0:
|
||||
/* No stage 1 entry? That means it's a new file */
|
||||
if (!same) {
|
||||
show_new_file(ce);
|
||||
break;
|
||||
}
|
||||
/* Show difference between old and new */
|
||||
show_modified(ac[1], ce, 1);
|
||||
break;
|
||||
case 1:
|
||||
/* No stage 3 (merge) entry? That means it's been deleted */
|
||||
if (!same) {
|
||||
show_file("-", ce, ce->sha1, ce->ce_mode);
|
||||
break;
|
||||
}
|
||||
/* We come here with ce pointing at stage 1
|
||||
* (original tree) and ac[1] pointing at stage
|
||||
* 3 (unmerged). show-modified with
|
||||
* report-missing set to false does not say the
|
||||
* file is deleted but reports true if work
|
||||
* tree does not have it, in which case we
|
||||
* fall through to report the unmerged state.
|
||||
* Otherwise, we show the differences between
|
||||
* the original tree and the work tree.
|
||||
*/
|
||||
if (!cached_only && !show_modified(ce, ac[1], 0))
|
||||
break;
|
||||
/* fallthru */
|
||||
case 3:
|
||||
diff_unmerge(&diff_options, ce->name);
|
||||
break;
|
||||
|
||||
default:
|
||||
die("impossible cache entry stage");
|
||||
}
|
||||
|
||||
skip_entry:
|
||||
/*
|
||||
* Ignore all the different stages for this file,
|
||||
* we've handled the relevant cases now.
|
||||
*/
|
||||
do {
|
||||
ac++;
|
||||
entries--;
|
||||
} while (entries && ce_same_name(ce, ac[0]));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* This turns all merge entries into "stage 3". That guarantees that
|
||||
* when we read in the new tree (into "stage 1"), we won't lose sight
|
||||
* of the fact that we had unmerged entries.
|
||||
*/
|
||||
static void mark_merge_entries(void)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < active_nr; i++) {
|
||||
struct cache_entry *ce = active_cache[i];
|
||||
if (!ce_stage(ce))
|
||||
continue;
|
||||
ce->ce_flags |= htons(CE_STAGEMASK);
|
||||
}
|
||||
}
|
||||
#include "commit.h"
|
||||
#include "revision.h"
|
||||
|
||||
static const char diff_cache_usage[] =
|
||||
"git-diff-index [-m] [--cached] "
|
||||
|
@ -169,85 +10,29 @@ COMMON_DIFF_OPTIONS_HELP;
|
|||
|
||||
int main(int argc, const char **argv)
|
||||
{
|
||||
const char *tree_name = NULL;
|
||||
unsigned char sha1[20];
|
||||
const char *prefix = setup_git_directory();
|
||||
const char **pathspec = NULL;
|
||||
struct tree *tree;
|
||||
int ret;
|
||||
int allow_options = 1;
|
||||
struct rev_info rev;
|
||||
int cached = 0;
|
||||
int i;
|
||||
|
||||
git_config(git_diff_config);
|
||||
diff_setup(&diff_options);
|
||||
init_revisions(&rev);
|
||||
rev.abbrev = 0;
|
||||
|
||||
argc = setup_revisions(argc, argv, &rev, NULL);
|
||||
for (i = 1; i < argc; i++) {
|
||||
const char *arg = argv[i];
|
||||
int diff_opt_cnt;
|
||||
|
||||
if (!allow_options || *arg != '-') {
|
||||
if (tree_name)
|
||||
break;
|
||||
tree_name = arg;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!strcmp(arg, "--")) {
|
||||
allow_options = 0;
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(arg, "-r")) {
|
||||
/* We accept the -r flag just to look like git-diff-tree */
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(arg, "--cc"))
|
||||
/*
|
||||
* I _think_ "diff-index --cached HEAD" with an
|
||||
* unmerged index could show something else
|
||||
* later, but pretend --cc is the same as -p for
|
||||
* now. "git diff" uses --cc by default.
|
||||
*/
|
||||
argv[i] = arg = "-p";
|
||||
diff_opt_cnt = diff_opt_parse(&diff_options, argv + i,
|
||||
argc - i);
|
||||
if (diff_opt_cnt < 0)
|
||||
if (!strcmp(arg, "--cached"))
|
||||
cached = 1;
|
||||
else
|
||||
usage(diff_cache_usage);
|
||||
else if (diff_opt_cnt) {
|
||||
i += diff_opt_cnt - 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!strcmp(arg, "-m")) {
|
||||
match_nonexisting = 1;
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(arg, "--cached")) {
|
||||
cached_only = 1;
|
||||
continue;
|
||||
}
|
||||
usage(diff_cache_usage);
|
||||
}
|
||||
|
||||
pathspec = get_pathspec(prefix, argv + i);
|
||||
|
||||
if (diff_setup_done(&diff_options) < 0)
|
||||
/*
|
||||
* Make sure there is one revision (i.e. pending object),
|
||||
* and there is no revision filtering parameters.
|
||||
*/
|
||||
if (!rev.pending_objects || rev.pending_objects->next ||
|
||||
rev.max_count != -1 || rev.min_age != -1 || rev.max_age != -1)
|
||||
usage(diff_cache_usage);
|
||||
|
||||
if (!tree_name || get_sha1(tree_name, sha1))
|
||||
usage(diff_cache_usage);
|
||||
|
||||
read_cache();
|
||||
|
||||
mark_merge_entries();
|
||||
|
||||
tree = parse_tree_indirect(sha1);
|
||||
if (!tree)
|
||||
die("bad tree object %s", tree_name);
|
||||
if (read_tree(tree, 1, pathspec))
|
||||
die("unable to read tree object %s", tree_name);
|
||||
|
||||
ret = diff_cache(active_cache, active_nr, pathspec);
|
||||
|
||||
diffcore_std(&diff_options);
|
||||
diff_flush(&diff_options);
|
||||
return ret;
|
||||
return run_diff_index(&rev, cached);
|
||||
}
|
||||
|
|
1958
diff-lib.c
1958
diff-lib.c
|
@ -1,1736 +1,344 @@
|
|||
/*
|
||||
* Copyright (C) 2005 Junio C Hamano
|
||||
*/
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <signal.h>
|
||||
#include "cache.h"
|
||||
#include "quote.h"
|
||||
#include "commit.h"
|
||||
#include "diff.h"
|
||||
#include "diffcore.h"
|
||||
#include "xdiff-interface.h"
|
||||
|
||||
static int use_size_cache;
|
||||
|
||||
int diff_rename_limit_default = -1;
|
||||
|
||||
int git_diff_config(const char *var, const char *value)
|
||||
{
|
||||
if (!strcmp(var, "diff.renamelimit")) {
|
||||
diff_rename_limit_default = git_config_int(var, value);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return git_default_config(var, value);
|
||||
}
|
||||
|
||||
static char *quote_one(const char *str)
|
||||
{
|
||||
int needlen;
|
||||
char *xp;
|
||||
|
||||
if (!str)
|
||||
return NULL;
|
||||
needlen = quote_c_style(str, NULL, NULL, 0);
|
||||
if (!needlen)
|
||||
return strdup(str);
|
||||
xp = xmalloc(needlen + 1);
|
||||
quote_c_style(str, xp, NULL, 0);
|
||||
return xp;
|
||||
}
|
||||
|
||||
static char *quote_two(const char *one, const char *two)
|
||||
{
|
||||
int need_one = quote_c_style(one, NULL, NULL, 1);
|
||||
int need_two = quote_c_style(two, NULL, NULL, 1);
|
||||
char *xp;
|
||||
|
||||
if (need_one + need_two) {
|
||||
if (!need_one) need_one = strlen(one);
|
||||
if (!need_two) need_one = strlen(two);
|
||||
|
||||
xp = xmalloc(need_one + need_two + 3);
|
||||
xp[0] = '"';
|
||||
quote_c_style(one, xp + 1, NULL, 1);
|
||||
quote_c_style(two, xp + need_one + 1, NULL, 1);
|
||||
strcpy(xp + need_one + need_two + 1, "\"");
|
||||
return xp;
|
||||
}
|
||||
need_one = strlen(one);
|
||||
need_two = strlen(two);
|
||||
xp = xmalloc(need_one + need_two + 1);
|
||||
strcpy(xp, one);
|
||||
strcpy(xp + need_one, two);
|
||||
return xp;
|
||||
}
|
||||
|
||||
static const char *external_diff(void)
|
||||
{
|
||||
static const char *external_diff_cmd = NULL;
|
||||
static int done_preparing = 0;
|
||||
|
||||
if (done_preparing)
|
||||
return external_diff_cmd;
|
||||
external_diff_cmd = getenv("GIT_EXTERNAL_DIFF");
|
||||
done_preparing = 1;
|
||||
return external_diff_cmd;
|
||||
}
|
||||
|
||||
#define TEMPFILE_PATH_LEN 50
|
||||
|
||||
static struct diff_tempfile {
|
||||
const char *name; /* filename external diff should read from */
|
||||
char hex[41];
|
||||
char mode[10];
|
||||
char tmp_path[TEMPFILE_PATH_LEN];
|
||||
} diff_temp[2];
|
||||
|
||||
static int count_lines(const char *data, int size)
|
||||
{
|
||||
int count, ch, completely_empty = 1, nl_just_seen = 0;
|
||||
count = 0;
|
||||
while (0 < size--) {
|
||||
ch = *data++;
|
||||
if (ch == '\n') {
|
||||
count++;
|
||||
nl_just_seen = 1;
|
||||
completely_empty = 0;
|
||||
}
|
||||
else {
|
||||
nl_just_seen = 0;
|
||||
completely_empty = 0;
|
||||
}
|
||||
}
|
||||
if (completely_empty)
|
||||
return 0;
|
||||
if (!nl_just_seen)
|
||||
count++; /* no trailing newline */
|
||||
return count;
|
||||
}
|
||||
|
||||
static void print_line_count(int count)
|
||||
{
|
||||
switch (count) {
|
||||
case 0:
|
||||
printf("0,0");
|
||||
break;
|
||||
case 1:
|
||||
printf("1");
|
||||
break;
|
||||
default:
|
||||
printf("1,%d", count);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void copy_file(int prefix, const char *data, int size)
|
||||
{
|
||||
int ch, nl_just_seen = 1;
|
||||
while (0 < size--) {
|
||||
ch = *data++;
|
||||
if (nl_just_seen)
|
||||
putchar(prefix);
|
||||
putchar(ch);
|
||||
if (ch == '\n')
|
||||
nl_just_seen = 1;
|
||||
else
|
||||
nl_just_seen = 0;
|
||||
}
|
||||
if (!nl_just_seen)
|
||||
printf("\n\\ No newline at end of file\n");
|
||||
}
|
||||
|
||||
static void emit_rewrite_diff(const char *name_a,
|
||||
const char *name_b,
|
||||
struct diff_filespec *one,
|
||||
struct diff_filespec *two)
|
||||
{
|
||||
int lc_a, lc_b;
|
||||
diff_populate_filespec(one, 0);
|
||||
diff_populate_filespec(two, 0);
|
||||
lc_a = count_lines(one->data, one->size);
|
||||
lc_b = count_lines(two->data, two->size);
|
||||
printf("--- %s\n+++ %s\n@@ -", name_a, name_b);
|
||||
print_line_count(lc_a);
|
||||
printf(" +");
|
||||
print_line_count(lc_b);
|
||||
printf(" @@\n");
|
||||
if (lc_a)
|
||||
copy_file('-', one->data, one->size);
|
||||
if (lc_b)
|
||||
copy_file('+', two->data, two->size);
|
||||
}
|
||||
|
||||
static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one)
|
||||
{
|
||||
if (!DIFF_FILE_VALID(one)) {
|
||||
mf->ptr = ""; /* does not matter */
|
||||
mf->size = 0;
|
||||
return 0;
|
||||
}
|
||||
else if (diff_populate_filespec(one, 0))
|
||||
return -1;
|
||||
mf->ptr = one->data;
|
||||
mf->size = one->size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct emit_callback {
|
||||
const char **label_path;
|
||||
};
|
||||
|
||||
static int fn_out(void *priv, mmbuffer_t *mb, int nbuf)
|
||||
{
|
||||
int i;
|
||||
struct emit_callback *ecbdata = priv;
|
||||
|
||||
if (ecbdata->label_path[0]) {
|
||||
printf("--- %s\n", ecbdata->label_path[0]);
|
||||
printf("+++ %s\n", ecbdata->label_path[1]);
|
||||
ecbdata->label_path[0] = ecbdata->label_path[1] = NULL;
|
||||
}
|
||||
for (i = 0; i < nbuf; i++)
|
||||
if (!fwrite(mb[i].ptr, mb[i].size, 1, stdout))
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct diffstat_t {
|
||||
struct xdiff_emit_state xm;
|
||||
|
||||
int nr;
|
||||
int alloc;
|
||||
struct diffstat_file {
|
||||
char *name;
|
||||
unsigned is_unmerged:1;
|
||||
unsigned is_binary:1;
|
||||
unsigned int added, deleted;
|
||||
} **files;
|
||||
};
|
||||
|
||||
static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat,
|
||||
const char *name)
|
||||
{
|
||||
struct diffstat_file *x;
|
||||
x = xcalloc(sizeof (*x), 1);
|
||||
if (diffstat->nr == diffstat->alloc) {
|
||||
diffstat->alloc = alloc_nr(diffstat->alloc);
|
||||
diffstat->files = xrealloc(diffstat->files,
|
||||
diffstat->alloc * sizeof(x));
|
||||
}
|
||||
diffstat->files[diffstat->nr++] = x;
|
||||
x->name = strdup(name);
|
||||
return x;
|
||||
}
|
||||
|
||||
static void diffstat_consume(void *priv, char *line, unsigned long len)
|
||||
{
|
||||
struct diffstat_t *diffstat = priv;
|
||||
struct diffstat_file *x = diffstat->files[diffstat->nr - 1];
|
||||
|
||||
if (line[0] == '+')
|
||||
x->added++;
|
||||
else if (line[0] == '-')
|
||||
x->deleted++;
|
||||
}
|
||||
|
||||
static const char pluses[] = "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++";
|
||||
static const char minuses[]= "----------------------------------------------------------------------";
|
||||
|
||||
static void show_stats(struct diffstat_t* data)
|
||||
{
|
||||
char *prefix = "";
|
||||
int i, len, add, del, total, adds = 0, dels = 0;
|
||||
int max, max_change = 0, max_len = 0;
|
||||
int total_files = data->nr;
|
||||
|
||||
if (data->nr == 0)
|
||||
return;
|
||||
|
||||
for (i = 0; i < data->nr; i++) {
|
||||
struct diffstat_file *file = data->files[i];
|
||||
|
||||
len = strlen(file->name);
|
||||
if (max_len < len)
|
||||
max_len = len;
|
||||
|
||||
if (file->is_binary || file->is_unmerged)
|
||||
continue;
|
||||
if (max_change < file->added + file->deleted)
|
||||
max_change = file->added + file->deleted;
|
||||
}
|
||||
|
||||
for (i = 0; i < data->nr; i++) {
|
||||
char *name = data->files[i]->name;
|
||||
int added = data->files[i]->added;
|
||||
int deleted = data->files[i]->deleted;
|
||||
|
||||
if (0 < (len = quote_c_style(name, NULL, NULL, 0))) {
|
||||
char *qname = xmalloc(len + 1);
|
||||
quote_c_style(name, qname, NULL, 0);
|
||||
free(name);
|
||||
data->files[i]->name = name = qname;
|
||||
}
|
||||
|
||||
/*
|
||||
* "scale" the filename
|
||||
*/
|
||||
len = strlen(name);
|
||||
max = max_len;
|
||||
if (max > 50)
|
||||
max = 50;
|
||||
if (len > max) {
|
||||
char *slash;
|
||||
prefix = "...";
|
||||
max -= 3;
|
||||
name += len - max;
|
||||
slash = strchr(name, '/');
|
||||
if (slash)
|
||||
name = slash;
|
||||
}
|
||||
len = max;
|
||||
|
||||
/*
|
||||
* scale the add/delete
|
||||
*/
|
||||
max = max_change;
|
||||
if (max + len > 70)
|
||||
max = 70 - len;
|
||||
|
||||
if (data->files[i]->is_binary) {
|
||||
printf(" %s%-*s | Bin\n", prefix, len, name);
|
||||
goto free_diffstat_file;
|
||||
}
|
||||
else if (data->files[i]->is_unmerged) {
|
||||
printf(" %s%-*s | Unmerged\n", prefix, len, name);
|
||||
goto free_diffstat_file;
|
||||
}
|
||||
else if (added + deleted == 0) {
|
||||
total_files--;
|
||||
goto free_diffstat_file;
|
||||
}
|
||||
|
||||
add = added;
|
||||
del = deleted;
|
||||
total = add + del;
|
||||
adds += add;
|
||||
dels += del;
|
||||
|
||||
if (max_change > 0) {
|
||||
total = (total * max + max_change / 2) / max_change;
|
||||
add = (add * max + max_change / 2) / max_change;
|
||||
del = total - add;
|
||||
}
|
||||
printf(" %s%-*s |%5d %.*s%.*s\n", prefix,
|
||||
len, name, added + deleted,
|
||||
add, pluses, del, minuses);
|
||||
free_diffstat_file:
|
||||
free(data->files[i]->name);
|
||||
free(data->files[i]);
|
||||
}
|
||||
free(data->files);
|
||||
printf(" %d files changed, %d insertions(+), %d deletions(-)\n",
|
||||
total_files, adds, dels);
|
||||
}
|
||||
|
||||
#define FIRST_FEW_BYTES 8000
|
||||
static int mmfile_is_binary(mmfile_t *mf)
|
||||
{
|
||||
long sz = mf->size;
|
||||
if (FIRST_FEW_BYTES < sz)
|
||||
sz = FIRST_FEW_BYTES;
|
||||
if (memchr(mf->ptr, 0, sz))
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void builtin_diff(const char *name_a,
|
||||
const char *name_b,
|
||||
struct diff_filespec *one,
|
||||
struct diff_filespec *two,
|
||||
const char *xfrm_msg,
|
||||
int complete_rewrite)
|
||||
{
|
||||
mmfile_t mf1, mf2;
|
||||
const char *lbl[2];
|
||||
char *a_one, *b_two;
|
||||
|
||||
a_one = quote_two("a/", name_a);
|
||||
b_two = quote_two("b/", name_b);
|
||||
lbl[0] = DIFF_FILE_VALID(one) ? a_one : "/dev/null";
|
||||
lbl[1] = DIFF_FILE_VALID(two) ? b_two : "/dev/null";
|
||||
printf("diff --git %s %s\n", a_one, b_two);
|
||||
if (lbl[0][0] == '/') {
|
||||
/* /dev/null */
|
||||
printf("new file mode %06o\n", two->mode);
|
||||
if (xfrm_msg && xfrm_msg[0])
|
||||
puts(xfrm_msg);
|
||||
}
|
||||
else if (lbl[1][0] == '/') {
|
||||
printf("deleted file mode %06o\n", one->mode);
|
||||
if (xfrm_msg && xfrm_msg[0])
|
||||
puts(xfrm_msg);
|
||||
}
|
||||
else {
|
||||
if (one->mode != two->mode) {
|
||||
printf("old mode %06o\n", one->mode);
|
||||
printf("new mode %06o\n", two->mode);
|
||||
}
|
||||
if (xfrm_msg && xfrm_msg[0])
|
||||
puts(xfrm_msg);
|
||||
/*
|
||||
* we do not run diff between different kind
|
||||
* of objects.
|
||||
*/
|
||||
if ((one->mode ^ two->mode) & S_IFMT)
|
||||
goto free_ab_and_return;
|
||||
if (complete_rewrite) {
|
||||
emit_rewrite_diff(name_a, name_b, one, two);
|
||||
goto free_ab_and_return;
|
||||
}
|
||||
}
|
||||
|
||||
if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
|
||||
die("unable to read files to diff");
|
||||
|
||||
if (mmfile_is_binary(&mf1) || mmfile_is_binary(&mf2))
|
||||
printf("Binary files %s and %s differ\n", lbl[0], lbl[1]);
|
||||
else {
|
||||
/* Crazy xdl interfaces.. */
|
||||
const char *diffopts = getenv("GIT_DIFF_OPTS");
|
||||
xpparam_t xpp;
|
||||
xdemitconf_t xecfg;
|
||||
xdemitcb_t ecb;
|
||||
struct emit_callback ecbdata;
|
||||
|
||||
ecbdata.label_path = lbl;
|
||||
xpp.flags = XDF_NEED_MINIMAL;
|
||||
xecfg.ctxlen = 3;
|
||||
xecfg.flags = XDL_EMIT_FUNCNAMES;
|
||||
if (!diffopts)
|
||||
;
|
||||
else if (!strncmp(diffopts, "--unified=", 10))
|
||||
xecfg.ctxlen = strtoul(diffopts + 10, NULL, 10);
|
||||
else if (!strncmp(diffopts, "-u", 2))
|
||||
xecfg.ctxlen = strtoul(diffopts + 2, NULL, 10);
|
||||
ecb.outf = fn_out;
|
||||
ecb.priv = &ecbdata;
|
||||
xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
|
||||
}
|
||||
|
||||
free_ab_and_return:
|
||||
free(a_one);
|
||||
free(b_two);
|
||||
return;
|
||||
}
|
||||
|
||||
static void builtin_diffstat(const char *name_a, const char *name_b,
|
||||
struct diff_filespec *one, struct diff_filespec *two,
|
||||
struct diffstat_t *diffstat)
|
||||
{
|
||||
mmfile_t mf1, mf2;
|
||||
struct diffstat_file *data;
|
||||
|
||||
data = diffstat_add(diffstat, name_a ? name_a : name_b);
|
||||
|
||||
if (!one || !two) {
|
||||
data->is_unmerged = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
|
||||
die("unable to read files to diff");
|
||||
|
||||
if (mmfile_is_binary(&mf1) || mmfile_is_binary(&mf2))
|
||||
data->is_binary = 1;
|
||||
else {
|
||||
/* Crazy xdl interfaces.. */
|
||||
xpparam_t xpp;
|
||||
xdemitconf_t xecfg;
|
||||
xdemitcb_t ecb;
|
||||
|
||||
xpp.flags = XDF_NEED_MINIMAL;
|
||||
xecfg.ctxlen = 0;
|
||||
xecfg.flags = 0;
|
||||
ecb.outf = xdiff_outf;
|
||||
ecb.priv = diffstat;
|
||||
xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
|
||||
}
|
||||
}
|
||||
|
||||
struct diff_filespec *alloc_filespec(const char *path)
|
||||
{
|
||||
int namelen = strlen(path);
|
||||
struct diff_filespec *spec = xmalloc(sizeof(*spec) + namelen + 1);
|
||||
|
||||
memset(spec, 0, sizeof(*spec));
|
||||
spec->path = (char *)(spec + 1);
|
||||
memcpy(spec->path, path, namelen+1);
|
||||
return spec;
|
||||
}
|
||||
|
||||
void fill_filespec(struct diff_filespec *spec, const unsigned char *sha1,
|
||||
unsigned short mode)
|
||||
{
|
||||
if (mode) {
|
||||
spec->mode = canon_mode(mode);
|
||||
memcpy(spec->sha1, sha1, 20);
|
||||
spec->sha1_valid = !!memcmp(sha1, null_sha1, 20);
|
||||
}
|
||||
}
|
||||
#include "revision.h"
|
||||
|
||||
/*
|
||||
* Given a name and sha1 pair, if the dircache tells us the file in
|
||||
* the work tree has that object contents, return true, so that
|
||||
* prepare_temp_file() does not have to inflate and extract.
|
||||
* diff-files
|
||||
*/
|
||||
static int work_tree_matches(const char *name, const unsigned char *sha1)
|
||||
|
||||
int run_diff_files(struct rev_info *revs, int silent_on_removed)
|
||||
{
|
||||
struct cache_entry *ce;
|
||||
struct stat st;
|
||||
int pos, len;
|
||||
int entries, i;
|
||||
int diff_unmerged_stage = revs->max_count;
|
||||
|
||||
/* We do not read the cache ourselves here, because the
|
||||
* benchmark with my previous version that always reads cache
|
||||
* shows that it makes things worse for diff-tree comparing
|
||||
* two linux-2.6 kernel trees in an already checked out work
|
||||
* tree. This is because most diff-tree comparisons deal with
|
||||
* only a small number of files, while reading the cache is
|
||||
* expensive for a large project, and its cost outweighs the
|
||||
* savings we get by not inflating the object to a temporary
|
||||
* file. Practically, this code only helps when we are used
|
||||
* by diff-cache --cached, which does read the cache before
|
||||
* calling us.
|
||||
*/
|
||||
if (!active_cache)
|
||||
return 0;
|
||||
|
||||
len = strlen(name);
|
||||
pos = cache_name_pos(name, len);
|
||||
if (pos < 0)
|
||||
return 0;
|
||||
ce = active_cache[pos];
|
||||
if ((lstat(name, &st) < 0) ||
|
||||
!S_ISREG(st.st_mode) || /* careful! */
|
||||
ce_match_stat(ce, &st, 0) ||
|
||||
memcmp(sha1, ce->sha1, 20))
|
||||
return 0;
|
||||
/* we return 1 only when we can stat, it is a regular file,
|
||||
* stat information matches, and sha1 recorded in the cache
|
||||
* matches. I.e. we know the file in the work tree really is
|
||||
* the same as the <name, sha1> pair.
|
||||
*/
|
||||
return 1;
|
||||
}
|
||||
|
||||
static struct sha1_size_cache {
|
||||
unsigned char sha1[20];
|
||||
unsigned long size;
|
||||
} **sha1_size_cache;
|
||||
static int sha1_size_cache_nr, sha1_size_cache_alloc;
|
||||
|
||||
static struct sha1_size_cache *locate_size_cache(unsigned char *sha1,
|
||||
int find_only,
|
||||
unsigned long size)
|
||||
{
|
||||
int first, last;
|
||||
struct sha1_size_cache *e;
|
||||
|
||||
first = 0;
|
||||
last = sha1_size_cache_nr;
|
||||
while (last > first) {
|
||||
int cmp, next = (last + first) >> 1;
|
||||
e = sha1_size_cache[next];
|
||||
cmp = memcmp(e->sha1, sha1, 20);
|
||||
if (!cmp)
|
||||
return e;
|
||||
if (cmp < 0) {
|
||||
last = next;
|
||||
continue;
|
||||
}
|
||||
first = next+1;
|
||||
}
|
||||
/* not found */
|
||||
if (find_only)
|
||||
return NULL;
|
||||
/* insert to make it at "first" */
|
||||
if (sha1_size_cache_alloc <= sha1_size_cache_nr) {
|
||||
sha1_size_cache_alloc = alloc_nr(sha1_size_cache_alloc);
|
||||
sha1_size_cache = xrealloc(sha1_size_cache,
|
||||
sha1_size_cache_alloc *
|
||||
sizeof(*sha1_size_cache));
|
||||
}
|
||||
sha1_size_cache_nr++;
|
||||
if (first < sha1_size_cache_nr)
|
||||
memmove(sha1_size_cache + first + 1, sha1_size_cache + first,
|
||||
(sha1_size_cache_nr - first - 1) *
|
||||
sizeof(*sha1_size_cache));
|
||||
e = xmalloc(sizeof(struct sha1_size_cache));
|
||||
sha1_size_cache[first] = e;
|
||||
memcpy(e->sha1, sha1, 20);
|
||||
e->size = size;
|
||||
return e;
|
||||
}
|
||||
|
||||
/*
|
||||
* While doing rename detection and pickaxe operation, we may need to
|
||||
* grab the data for the blob (or file) for our own in-core comparison.
|
||||
* diff_filespec has data and size fields for this purpose.
|
||||
*/
|
||||
int diff_populate_filespec(struct diff_filespec *s, int size_only)
|
||||
{
|
||||
int err = 0;
|
||||
if (!DIFF_FILE_VALID(s))
|
||||
die("internal error: asking to populate invalid file.");
|
||||
if (S_ISDIR(s->mode))
|
||||
if (diff_unmerged_stage < 0)
|
||||
diff_unmerged_stage = 2;
|
||||
entries = read_cache();
|
||||
if (entries < 0) {
|
||||
perror("read_cache");
|
||||
return -1;
|
||||
|
||||
if (!use_size_cache)
|
||||
size_only = 0;
|
||||
|
||||
if (s->data)
|
||||
return err;
|
||||
if (!s->sha1_valid ||
|
||||
work_tree_matches(s->path, s->sha1)) {
|
||||
}
|
||||
for (i = 0; i < entries; i++) {
|
||||
struct stat st;
|
||||
int fd;
|
||||
if (lstat(s->path, &st) < 0) {
|
||||
if (errno == ENOENT) {
|
||||
err_empty:
|
||||
err = -1;
|
||||
empty:
|
||||
s->data = "";
|
||||
s->size = 0;
|
||||
return err;
|
||||
}
|
||||
}
|
||||
s->size = st.st_size;
|
||||
if (!s->size)
|
||||
goto empty;
|
||||
if (size_only)
|
||||
return 0;
|
||||
if (S_ISLNK(st.st_mode)) {
|
||||
int ret;
|
||||
s->data = xmalloc(s->size);
|
||||
s->should_free = 1;
|
||||
ret = readlink(s->path, s->data, s->size);
|
||||
if (ret < 0) {
|
||||
free(s->data);
|
||||
goto err_empty;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
fd = open(s->path, O_RDONLY);
|
||||
if (fd < 0)
|
||||
goto err_empty;
|
||||
s->data = mmap(NULL, s->size, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||
close(fd);
|
||||
if (s->data == MAP_FAILED)
|
||||
goto err_empty;
|
||||
s->should_munmap = 1;
|
||||
}
|
||||
else {
|
||||
char type[20];
|
||||
struct sha1_size_cache *e;
|
||||
unsigned int oldmode, newmode;
|
||||
struct cache_entry *ce = active_cache[i];
|
||||
int changed;
|
||||
|
||||
if (size_only) {
|
||||
e = locate_size_cache(s->sha1, 1, 0);
|
||||
if (e) {
|
||||
s->size = e->size;
|
||||
return 0;
|
||||
}
|
||||
if (!sha1_object_info(s->sha1, type, &s->size))
|
||||
locate_size_cache(s->sha1, 0, s->size);
|
||||
}
|
||||
else {
|
||||
s->data = read_sha1_file(s->sha1, type, &s->size);
|
||||
s->should_free = 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void diff_free_filespec_data(struct diff_filespec *s)
|
||||
{
|
||||
if (s->should_free)
|
||||
free(s->data);
|
||||
else if (s->should_munmap)
|
||||
munmap(s->data, s->size);
|
||||
s->should_free = s->should_munmap = 0;
|
||||
s->data = NULL;
|
||||
free(s->cnt_data);
|
||||
s->cnt_data = NULL;
|
||||
}
|
||||
|
||||
static void prep_temp_blob(struct diff_tempfile *temp,
|
||||
void *blob,
|
||||
unsigned long size,
|
||||
const unsigned char *sha1,
|
||||
int mode)
|
||||
{
|
||||
int fd;
|
||||
|
||||
fd = git_mkstemp(temp->tmp_path, TEMPFILE_PATH_LEN, ".diff_XXXXXX");
|
||||
if (fd < 0)
|
||||
die("unable to create temp-file");
|
||||
if (write(fd, blob, size) != size)
|
||||
die("unable to write temp-file");
|
||||
close(fd);
|
||||
temp->name = temp->tmp_path;
|
||||
strcpy(temp->hex, sha1_to_hex(sha1));
|
||||
temp->hex[40] = 0;
|
||||
sprintf(temp->mode, "%06o", mode);
|
||||
}
|
||||
|
||||
static void prepare_temp_file(const char *name,
|
||||
struct diff_tempfile *temp,
|
||||
struct diff_filespec *one)
|
||||
{
|
||||
if (!DIFF_FILE_VALID(one)) {
|
||||
not_a_valid_file:
|
||||
/* A '-' entry produces this for file-2, and
|
||||
* a '+' entry produces this for file-1.
|
||||
*/
|
||||
temp->name = "/dev/null";
|
||||
strcpy(temp->hex, ".");
|
||||
strcpy(temp->mode, ".");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!one->sha1_valid ||
|
||||
work_tree_matches(name, one->sha1)) {
|
||||
struct stat st;
|
||||
if (lstat(name, &st) < 0) {
|
||||
if (errno == ENOENT)
|
||||
goto not_a_valid_file;
|
||||
die("stat(%s): %s", name, strerror(errno));
|
||||
}
|
||||
if (S_ISLNK(st.st_mode)) {
|
||||
int ret;
|
||||
char buf[PATH_MAX + 1]; /* ought to be SYMLINK_MAX */
|
||||
if (sizeof(buf) <= st.st_size)
|
||||
die("symlink too long: %s", name);
|
||||
ret = readlink(name, buf, st.st_size);
|
||||
if (ret < 0)
|
||||
die("readlink(%s)", name);
|
||||
prep_temp_blob(temp, buf, st.st_size,
|
||||
(one->sha1_valid ?
|
||||
one->sha1 : null_sha1),
|
||||
(one->sha1_valid ?
|
||||
one->mode : S_IFLNK));
|
||||
}
|
||||
else {
|
||||
/* we can borrow from the file in the work tree */
|
||||
temp->name = name;
|
||||
if (!one->sha1_valid)
|
||||
strcpy(temp->hex, sha1_to_hex(null_sha1));
|
||||
else
|
||||
strcpy(temp->hex, sha1_to_hex(one->sha1));
|
||||
/* Even though we may sometimes borrow the
|
||||
* contents from the work tree, we always want
|
||||
* one->mode. mode is trustworthy even when
|
||||
* !(one->sha1_valid), as long as
|
||||
* DIFF_FILE_VALID(one).
|
||||
*/
|
||||
sprintf(temp->mode, "%06o", one->mode);
|
||||
}
|
||||
return;
|
||||
}
|
||||
else {
|
||||
if (diff_populate_filespec(one, 0))
|
||||
die("cannot read data blob for %s", one->path);
|
||||
prep_temp_blob(temp, one->data, one->size,
|
||||
one->sha1, one->mode);
|
||||
}
|
||||
}
|
||||
|
||||
static void remove_tempfile(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 2; i++)
|
||||
if (diff_temp[i].name == diff_temp[i].tmp_path) {
|
||||
unlink(diff_temp[i].name);
|
||||
diff_temp[i].name = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void remove_tempfile_on_signal(int signo)
|
||||
{
|
||||
remove_tempfile();
|
||||
signal(SIGINT, SIG_DFL);
|
||||
raise(signo);
|
||||
}
|
||||
|
||||
static int spawn_prog(const char *pgm, const char **arg)
|
||||
{
|
||||
pid_t pid;
|
||||
int status;
|
||||
|
||||
fflush(NULL);
|
||||
pid = fork();
|
||||
if (pid < 0)
|
||||
die("unable to fork");
|
||||
if (!pid) {
|
||||
execvp(pgm, (char *const*) arg);
|
||||
exit(255);
|
||||
}
|
||||
|
||||
while (waitpid(pid, &status, 0) < 0) {
|
||||
if (errno == EINTR)
|
||||
if (!ce_path_match(ce, revs->prune_data))
|
||||
continue;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Earlier we did not check the exit status because
|
||||
* diff exits non-zero if files are different, and
|
||||
* we are not interested in knowing that. It was a
|
||||
* mistake which made it harder to quit a diff-*
|
||||
* session that uses the git-apply-patch-script as
|
||||
* the GIT_EXTERNAL_DIFF. A custom GIT_EXTERNAL_DIFF
|
||||
* should also exit non-zero only when it wants to
|
||||
* abort the entire diff-* session.
|
||||
*/
|
||||
if (WIFEXITED(status) && !WEXITSTATUS(status))
|
||||
return 0;
|
||||
return -1;
|
||||
}
|
||||
if (ce_stage(ce)) {
|
||||
struct {
|
||||
struct combine_diff_path p;
|
||||
struct combine_diff_parent filler[5];
|
||||
} combine;
|
||||
int num_compare_stages = 0;
|
||||
|
||||
/* An external diff command takes:
|
||||
*
|
||||
* diff-cmd name infile1 infile1-sha1 infile1-mode \
|
||||
* infile2 infile2-sha1 infile2-mode [ rename-to ]
|
||||
*
|
||||
*/
|
||||
static void run_external_diff(const char *pgm,
|
||||
const char *name,
|
||||
const char *other,
|
||||
struct diff_filespec *one,
|
||||
struct diff_filespec *two,
|
||||
const char *xfrm_msg,
|
||||
int complete_rewrite)
|
||||
{
|
||||
const char *spawn_arg[10];
|
||||
struct diff_tempfile *temp = diff_temp;
|
||||
int retval;
|
||||
static int atexit_asked = 0;
|
||||
const char *othername;
|
||||
const char **arg = &spawn_arg[0];
|
||||
combine.p.next = NULL;
|
||||
combine.p.len = ce_namelen(ce);
|
||||
combine.p.path = xmalloc(combine.p.len + 1);
|
||||
memcpy(combine.p.path, ce->name, combine.p.len);
|
||||
combine.p.path[combine.p.len] = 0;
|
||||
combine.p.mode = 0;
|
||||
memset(combine.p.sha1, 0, 20);
|
||||
memset(&combine.p.parent[0], 0,
|
||||
sizeof(combine.filler));
|
||||
|
||||
othername = (other? other : name);
|
||||
if (one && two) {
|
||||
prepare_temp_file(name, &temp[0], one);
|
||||
prepare_temp_file(othername, &temp[1], two);
|
||||
if (! atexit_asked &&
|
||||
(temp[0].name == temp[0].tmp_path ||
|
||||
temp[1].name == temp[1].tmp_path)) {
|
||||
atexit_asked = 1;
|
||||
atexit(remove_tempfile);
|
||||
}
|
||||
signal(SIGINT, remove_tempfile_on_signal);
|
||||
}
|
||||
while (i < entries) {
|
||||
struct cache_entry *nce = active_cache[i];
|
||||
int stage;
|
||||
|
||||
if (one && two) {
|
||||
*arg++ = pgm;
|
||||
*arg++ = name;
|
||||
*arg++ = temp[0].name;
|
||||
*arg++ = temp[0].hex;
|
||||
*arg++ = temp[0].mode;
|
||||
*arg++ = temp[1].name;
|
||||
*arg++ = temp[1].hex;
|
||||
*arg++ = temp[1].mode;
|
||||
if (other) {
|
||||
*arg++ = other;
|
||||
*arg++ = xfrm_msg;
|
||||
}
|
||||
} else {
|
||||
*arg++ = pgm;
|
||||
*arg++ = name;
|
||||
}
|
||||
*arg = NULL;
|
||||
retval = spawn_prog(pgm, spawn_arg);
|
||||
remove_tempfile();
|
||||
if (retval) {
|
||||
fprintf(stderr, "external diff died, stopping at %s.\n", name);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
if (strcmp(ce->name, nce->name))
|
||||
break;
|
||||
|
||||
static void run_diff_cmd(const char *pgm,
|
||||
const char *name,
|
||||
const char *other,
|
||||
struct diff_filespec *one,
|
||||
struct diff_filespec *two,
|
||||
const char *xfrm_msg,
|
||||
int complete_rewrite)
|
||||
{
|
||||
if (pgm) {
|
||||
run_external_diff(pgm, name, other, one, two, xfrm_msg,
|
||||
complete_rewrite);
|
||||
return;
|
||||
}
|
||||
if (one && two)
|
||||
builtin_diff(name, other ? other : name,
|
||||
one, two, xfrm_msg, complete_rewrite);
|
||||
else
|
||||
printf("* Unmerged path %s\n", name);
|
||||
}
|
||||
/* Stage #2 (ours) is the first parent,
|
||||
* stage #3 (theirs) is the second.
|
||||
*/
|
||||
stage = ce_stage(nce);
|
||||
if (2 <= stage) {
|
||||
int mode = ntohl(nce->ce_mode);
|
||||
num_compare_stages++;
|
||||
memcpy(combine.p.parent[stage-2].sha1,
|
||||
nce->sha1, 20);
|
||||
combine.p.parent[stage-2].mode =
|
||||
canon_mode(mode);
|
||||
combine.p.parent[stage-2].status =
|
||||
DIFF_STATUS_MODIFIED;
|
||||
}
|
||||
|
||||
static void diff_fill_sha1_info(struct diff_filespec *one)
|
||||
{
|
||||
if (DIFF_FILE_VALID(one)) {
|
||||
if (!one->sha1_valid) {
|
||||
struct stat st;
|
||||
if (lstat(one->path, &st) < 0)
|
||||
die("stat %s", one->path);
|
||||
if (index_path(one->sha1, one->path, &st, 0))
|
||||
die("cannot hash %s\n", one->path);
|
||||
}
|
||||
}
|
||||
else
|
||||
memset(one->sha1, 0, 20);
|
||||
}
|
||||
|
||||
static void run_diff(struct diff_filepair *p, struct diff_options *o)
|
||||
{
|
||||
const char *pgm = external_diff();
|
||||
char msg[PATH_MAX*2+300], *xfrm_msg;
|
||||
struct diff_filespec *one;
|
||||
struct diff_filespec *two;
|
||||
const char *name;
|
||||
const char *other;
|
||||
char *name_munged, *other_munged;
|
||||
int complete_rewrite = 0;
|
||||
int len;
|
||||
|
||||
if (DIFF_PAIR_UNMERGED(p)) {
|
||||
/* unmerged */
|
||||
run_diff_cmd(pgm, p->one->path, NULL, NULL, NULL, NULL, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
name = p->one->path;
|
||||
other = (strcmp(name, p->two->path) ? p->two->path : NULL);
|
||||
name_munged = quote_one(name);
|
||||
other_munged = quote_one(other);
|
||||
one = p->one; two = p->two;
|
||||
|
||||
diff_fill_sha1_info(one);
|
||||
diff_fill_sha1_info(two);
|
||||
|
||||
len = 0;
|
||||
switch (p->status) {
|
||||
case DIFF_STATUS_COPIED:
|
||||
len += snprintf(msg + len, sizeof(msg) - len,
|
||||
"similarity index %d%%\n"
|
||||
"copy from %s\n"
|
||||
"copy to %s\n",
|
||||
(int)(0.5 + p->score * 100.0/MAX_SCORE),
|
||||
name_munged, other_munged);
|
||||
break;
|
||||
case DIFF_STATUS_RENAMED:
|
||||
len += snprintf(msg + len, sizeof(msg) - len,
|
||||
"similarity index %d%%\n"
|
||||
"rename from %s\n"
|
||||
"rename to %s\n",
|
||||
(int)(0.5 + p->score * 100.0/MAX_SCORE),
|
||||
name_munged, other_munged);
|
||||
break;
|
||||
case DIFF_STATUS_MODIFIED:
|
||||
if (p->score) {
|
||||
len += snprintf(msg + len, sizeof(msg) - len,
|
||||
"dissimilarity index %d%%\n",
|
||||
(int)(0.5 + p->score *
|
||||
100.0/MAX_SCORE));
|
||||
complete_rewrite = 1;
|
||||
break;
|
||||
}
|
||||
/* fallthru */
|
||||
default:
|
||||
/* nothing */
|
||||
;
|
||||
}
|
||||
|
||||
if (memcmp(one->sha1, two->sha1, 20)) {
|
||||
char one_sha1[41];
|
||||
int abbrev = o->full_index ? 40 : DEFAULT_ABBREV;
|
||||
memcpy(one_sha1, sha1_to_hex(one->sha1), 41);
|
||||
|
||||
len += snprintf(msg + len, sizeof(msg) - len,
|
||||
"index %.*s..%.*s",
|
||||
abbrev, one_sha1, abbrev,
|
||||
sha1_to_hex(two->sha1));
|
||||
if (one->mode == two->mode)
|
||||
len += snprintf(msg + len, sizeof(msg) - len,
|
||||
" %06o", one->mode);
|
||||
len += snprintf(msg + len, sizeof(msg) - len, "\n");
|
||||
}
|
||||
|
||||
if (len)
|
||||
msg[--len] = 0;
|
||||
xfrm_msg = len ? msg : NULL;
|
||||
|
||||
if (!pgm &&
|
||||
DIFF_FILE_VALID(one) && DIFF_FILE_VALID(two) &&
|
||||
(S_IFMT & one->mode) != (S_IFMT & two->mode)) {
|
||||
/* a filepair that changes between file and symlink
|
||||
* needs to be split into deletion and creation.
|
||||
*/
|
||||
struct diff_filespec *null = alloc_filespec(two->path);
|
||||
run_diff_cmd(NULL, name, other, one, null, xfrm_msg, 0);
|
||||
free(null);
|
||||
null = alloc_filespec(one->path);
|
||||
run_diff_cmd(NULL, name, other, null, two, xfrm_msg, 0);
|
||||
free(null);
|
||||
}
|
||||
else
|
||||
run_diff_cmd(pgm, name, other, one, two, xfrm_msg,
|
||||
complete_rewrite);
|
||||
|
||||
free(name_munged);
|
||||
free(other_munged);
|
||||
}
|
||||
|
||||
static void run_diffstat(struct diff_filepair *p, struct diff_options *o,
|
||||
struct diffstat_t *diffstat)
|
||||
{
|
||||
const char *name;
|
||||
const char *other;
|
||||
|
||||
if (DIFF_PAIR_UNMERGED(p)) {
|
||||
/* unmerged */
|
||||
builtin_diffstat(p->one->path, NULL, NULL, NULL, diffstat);
|
||||
return;
|
||||
}
|
||||
|
||||
name = p->one->path;
|
||||
other = (strcmp(name, p->two->path) ? p->two->path : NULL);
|
||||
|
||||
diff_fill_sha1_info(p->one);
|
||||
diff_fill_sha1_info(p->two);
|
||||
|
||||
builtin_diffstat(name, other, p->one, p->two, diffstat);
|
||||
}
|
||||
|
||||
void diff_setup(struct diff_options *options)
|
||||
{
|
||||
memset(options, 0, sizeof(*options));
|
||||
options->output_format = DIFF_FORMAT_RAW;
|
||||
options->line_termination = '\n';
|
||||
options->break_opt = -1;
|
||||
options->rename_limit = -1;
|
||||
|
||||
options->change = diff_change;
|
||||
options->add_remove = diff_addremove;
|
||||
}
|
||||
|
||||
int diff_setup_done(struct diff_options *options)
|
||||
{
|
||||
if ((options->find_copies_harder &&
|
||||
options->detect_rename != DIFF_DETECT_COPY) ||
|
||||
(0 <= options->rename_limit && !options->detect_rename))
|
||||
return -1;
|
||||
|
||||
/*
|
||||
* These cases always need recursive; we do not drop caller-supplied
|
||||
* recursive bits for other formats here.
|
||||
*/
|
||||
if ((options->output_format == DIFF_FORMAT_PATCH) ||
|
||||
(options->output_format == DIFF_FORMAT_DIFFSTAT))
|
||||
options->recursive = 1;
|
||||
|
||||
if (options->detect_rename && options->rename_limit < 0)
|
||||
options->rename_limit = diff_rename_limit_default;
|
||||
if (options->setup & DIFF_SETUP_USE_CACHE) {
|
||||
if (!active_cache)
|
||||
/* read-cache does not die even when it fails
|
||||
* so it is safe for us to do this here. Also
|
||||
* it does not smudge active_cache or active_nr
|
||||
* when it fails, so we do not have to worry about
|
||||
* cleaning it up ourselves either.
|
||||
*/
|
||||
read_cache();
|
||||
}
|
||||
if (options->setup & DIFF_SETUP_USE_SIZE_CACHE)
|
||||
use_size_cache = 1;
|
||||
if (options->abbrev <= 0 || 40 < options->abbrev)
|
||||
options->abbrev = 40; /* full */
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int diff_opt_parse(struct diff_options *options, const char **av, int ac)
|
||||
{
|
||||
const char *arg = av[0];
|
||||
if (!strcmp(arg, "-p") || !strcmp(arg, "-u"))
|
||||
options->output_format = DIFF_FORMAT_PATCH;
|
||||
else if (!strcmp(arg, "--patch-with-raw")) {
|
||||
options->output_format = DIFF_FORMAT_PATCH;
|
||||
options->with_raw = 1;
|
||||
}
|
||||
else if (!strcmp(arg, "--stat"))
|
||||
options->output_format = DIFF_FORMAT_DIFFSTAT;
|
||||
else if (!strcmp(arg, "--patch-with-stat")) {
|
||||
options->output_format = DIFF_FORMAT_PATCH;
|
||||
options->with_stat = 1;
|
||||
}
|
||||
else if (!strcmp(arg, "-z"))
|
||||
options->line_termination = 0;
|
||||
else if (!strncmp(arg, "-l", 2))
|
||||
options->rename_limit = strtoul(arg+2, NULL, 10);
|
||||
else if (!strcmp(arg, "--full-index"))
|
||||
options->full_index = 1;
|
||||
else if (!strcmp(arg, "--name-only"))
|
||||
options->output_format = DIFF_FORMAT_NAME;
|
||||
else if (!strcmp(arg, "--name-status"))
|
||||
options->output_format = DIFF_FORMAT_NAME_STATUS;
|
||||
else if (!strcmp(arg, "-R"))
|
||||
options->reverse_diff = 1;
|
||||
else if (!strncmp(arg, "-S", 2))
|
||||
options->pickaxe = arg + 2;
|
||||
else if (!strcmp(arg, "-s"))
|
||||
options->output_format = DIFF_FORMAT_NO_OUTPUT;
|
||||
else if (!strncmp(arg, "-O", 2))
|
||||
options->orderfile = arg + 2;
|
||||
else if (!strncmp(arg, "--diff-filter=", 14))
|
||||
options->filter = arg + 14;
|
||||
else if (!strcmp(arg, "--pickaxe-all"))
|
||||
options->pickaxe_opts = DIFF_PICKAXE_ALL;
|
||||
else if (!strcmp(arg, "--pickaxe-regex"))
|
||||
options->pickaxe_opts = DIFF_PICKAXE_REGEX;
|
||||
else if (!strncmp(arg, "-B", 2)) {
|
||||
if ((options->break_opt =
|
||||
diff_scoreopt_parse(arg)) == -1)
|
||||
return -1;
|
||||
}
|
||||
else if (!strncmp(arg, "-M", 2)) {
|
||||
if ((options->rename_score =
|
||||
diff_scoreopt_parse(arg)) == -1)
|
||||
return -1;
|
||||
options->detect_rename = DIFF_DETECT_RENAME;
|
||||
}
|
||||
else if (!strncmp(arg, "-C", 2)) {
|
||||
if ((options->rename_score =
|
||||
diff_scoreopt_parse(arg)) == -1)
|
||||
return -1;
|
||||
options->detect_rename = DIFF_DETECT_COPY;
|
||||
}
|
||||
else if (!strcmp(arg, "--find-copies-harder"))
|
||||
options->find_copies_harder = 1;
|
||||
else if (!strcmp(arg, "--abbrev"))
|
||||
options->abbrev = DEFAULT_ABBREV;
|
||||
else if (!strncmp(arg, "--abbrev=", 9)) {
|
||||
options->abbrev = strtoul(arg + 9, NULL, 10);
|
||||
if (options->abbrev < MINIMUM_ABBREV)
|
||||
options->abbrev = MINIMUM_ABBREV;
|
||||
else if (40 < options->abbrev)
|
||||
options->abbrev = 40;
|
||||
}
|
||||
else
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int parse_num(const char **cp_p)
|
||||
{
|
||||
unsigned long num, scale;
|
||||
int ch, dot;
|
||||
const char *cp = *cp_p;
|
||||
|
||||
num = 0;
|
||||
scale = 1;
|
||||
dot = 0;
|
||||
for(;;) {
|
||||
ch = *cp;
|
||||
if ( !dot && ch == '.' ) {
|
||||
scale = 1;
|
||||
dot = 1;
|
||||
} else if ( ch == '%' ) {
|
||||
scale = dot ? scale*100 : 100;
|
||||
cp++; /* % is always at the end */
|
||||
break;
|
||||
} else if ( ch >= '0' && ch <= '9' ) {
|
||||
if ( scale < 100000 ) {
|
||||
scale *= 10;
|
||||
num = (num*10) + (ch-'0');
|
||||
/* diff against the proper unmerged stage */
|
||||
if (stage == diff_unmerged_stage)
|
||||
ce = nce;
|
||||
i++;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
cp++;
|
||||
}
|
||||
*cp_p = cp;
|
||||
/*
|
||||
* Compensate for loop update
|
||||
*/
|
||||
i--;
|
||||
|
||||
/* user says num divided by scale and we say internally that
|
||||
* is MAX_SCORE * num / scale.
|
||||
*/
|
||||
return (num >= scale) ? MAX_SCORE : (MAX_SCORE * num / scale);
|
||||
}
|
||||
|
||||
int diff_scoreopt_parse(const char *opt)
|
||||
{
|
||||
int opt1, opt2, cmd;
|
||||
|
||||
if (*opt++ != '-')
|
||||
return -1;
|
||||
cmd = *opt++;
|
||||
if (cmd != 'M' && cmd != 'C' && cmd != 'B')
|
||||
return -1; /* that is not a -M, -C nor -B option */
|
||||
|
||||
opt1 = parse_num(&opt);
|
||||
if (cmd != 'B')
|
||||
opt2 = 0;
|
||||
else {
|
||||
if (*opt == 0)
|
||||
opt2 = 0;
|
||||
else if (*opt != '/')
|
||||
return -1; /* we expect -B80/99 or -B80 */
|
||||
else {
|
||||
opt++;
|
||||
opt2 = parse_num(&opt);
|
||||
}
|
||||
}
|
||||
if (*opt != 0)
|
||||
return -1;
|
||||
return opt1 | (opt2 << 16);
|
||||
}
|
||||
|
||||
struct diff_queue_struct diff_queued_diff;
|
||||
|
||||
void diff_q(struct diff_queue_struct *queue, struct diff_filepair *dp)
|
||||
{
|
||||
if (queue->alloc <= queue->nr) {
|
||||
queue->alloc = alloc_nr(queue->alloc);
|
||||
queue->queue = xrealloc(queue->queue,
|
||||
sizeof(dp) * queue->alloc);
|
||||
}
|
||||
queue->queue[queue->nr++] = dp;
|
||||
}
|
||||
|
||||
struct diff_filepair *diff_queue(struct diff_queue_struct *queue,
|
||||
struct diff_filespec *one,
|
||||
struct diff_filespec *two)
|
||||
{
|
||||
struct diff_filepair *dp = xmalloc(sizeof(*dp));
|
||||
dp->one = one;
|
||||
dp->two = two;
|
||||
dp->score = 0;
|
||||
dp->status = 0;
|
||||
dp->source_stays = 0;
|
||||
dp->broken_pair = 0;
|
||||
if (queue)
|
||||
diff_q(queue, dp);
|
||||
return dp;
|
||||
}
|
||||
|
||||
void diff_free_filepair(struct diff_filepair *p)
|
||||
{
|
||||
diff_free_filespec_data(p->one);
|
||||
diff_free_filespec_data(p->two);
|
||||
free(p->one);
|
||||
free(p->two);
|
||||
free(p);
|
||||
}
|
||||
|
||||
/* This is different from find_unique_abbrev() in that
|
||||
* it stuffs the result with dots for alignment.
|
||||
*/
|
||||
const char *diff_unique_abbrev(const unsigned char *sha1, int len)
|
||||
{
|
||||
int abblen;
|
||||
const char *abbrev;
|
||||
if (len == 40)
|
||||
return sha1_to_hex(sha1);
|
||||
|
||||
abbrev = find_unique_abbrev(sha1, len);
|
||||
if (!abbrev)
|
||||
return sha1_to_hex(sha1);
|
||||
abblen = strlen(abbrev);
|
||||
if (abblen < 37) {
|
||||
static char hex[41];
|
||||
if (len < abblen && abblen <= len + 2)
|
||||
sprintf(hex, "%s%.*s", abbrev, len+3-abblen, "..");
|
||||
else
|
||||
sprintf(hex, "%s...", abbrev);
|
||||
return hex;
|
||||
}
|
||||
return sha1_to_hex(sha1);
|
||||
}
|
||||
|
||||
static void diff_flush_raw(struct diff_filepair *p,
|
||||
int line_termination,
|
||||
int inter_name_termination,
|
||||
struct diff_options *options,
|
||||
int output_format)
|
||||
{
|
||||
int two_paths;
|
||||
char status[10];
|
||||
int abbrev = options->abbrev;
|
||||
const char *path_one, *path_two;
|
||||
|
||||
path_one = p->one->path;
|
||||
path_two = p->two->path;
|
||||
if (line_termination) {
|
||||
path_one = quote_one(path_one);
|
||||
path_two = quote_one(path_two);
|
||||
}
|
||||
|
||||
if (p->score)
|
||||
sprintf(status, "%c%03d", p->status,
|
||||
(int)(0.5 + p->score * 100.0/MAX_SCORE));
|
||||
else {
|
||||
status[0] = p->status;
|
||||
status[1] = 0;
|
||||
}
|
||||
switch (p->status) {
|
||||
case DIFF_STATUS_COPIED:
|
||||
case DIFF_STATUS_RENAMED:
|
||||
two_paths = 1;
|
||||
break;
|
||||
case DIFF_STATUS_ADDED:
|
||||
case DIFF_STATUS_DELETED:
|
||||
two_paths = 0;
|
||||
break;
|
||||
default:
|
||||
two_paths = 0;
|
||||
break;
|
||||
}
|
||||
if (output_format != DIFF_FORMAT_NAME_STATUS) {
|
||||
printf(":%06o %06o %s ",
|
||||
p->one->mode, p->two->mode,
|
||||
diff_unique_abbrev(p->one->sha1, abbrev));
|
||||
printf("%s ",
|
||||
diff_unique_abbrev(p->two->sha1, abbrev));
|
||||
}
|
||||
printf("%s%c%s", status, inter_name_termination, path_one);
|
||||
if (two_paths)
|
||||
printf("%c%s", inter_name_termination, path_two);
|
||||
putchar(line_termination);
|
||||
if (path_one != p->one->path)
|
||||
free((void*)path_one);
|
||||
if (path_two != p->two->path)
|
||||
free((void*)path_two);
|
||||
}
|
||||
|
||||
static void diff_flush_name(struct diff_filepair *p,
|
||||
int inter_name_termination,
|
||||
int line_termination)
|
||||
{
|
||||
char *path = p->two->path;
|
||||
|
||||
if (line_termination)
|
||||
path = quote_one(p->two->path);
|
||||
else
|
||||
path = p->two->path;
|
||||
printf("%s%c", path, line_termination);
|
||||
if (p->two->path != path)
|
||||
free(path);
|
||||
}
|
||||
|
||||
int diff_unmodified_pair(struct diff_filepair *p)
|
||||
{
|
||||
/* This function is written stricter than necessary to support
|
||||
* the currently implemented transformers, but the idea is to
|
||||
* let transformers to produce diff_filepairs any way they want,
|
||||
* and filter and clean them up here before producing the output.
|
||||
*/
|
||||
struct diff_filespec *one, *two;
|
||||
|
||||
if (DIFF_PAIR_UNMERGED(p))
|
||||
return 0; /* unmerged is interesting */
|
||||
|
||||
one = p->one;
|
||||
two = p->two;
|
||||
|
||||
/* deletion, addition, mode or type change
|
||||
* and rename are all interesting.
|
||||
*/
|
||||
if (DIFF_FILE_VALID(one) != DIFF_FILE_VALID(two) ||
|
||||
DIFF_PAIR_MODE_CHANGED(p) ||
|
||||
strcmp(one->path, two->path))
|
||||
return 0;
|
||||
|
||||
/* both are valid and point at the same path. that is, we are
|
||||
* dealing with a change.
|
||||
*/
|
||||
if (one->sha1_valid && two->sha1_valid &&
|
||||
!memcmp(one->sha1, two->sha1, sizeof(one->sha1)))
|
||||
return 1; /* no change */
|
||||
if (!one->sha1_valid && !two->sha1_valid)
|
||||
return 1; /* both look at the same file on the filesystem. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void diff_flush_patch(struct diff_filepair *p, struct diff_options *o)
|
||||
{
|
||||
if (diff_unmodified_pair(p))
|
||||
return;
|
||||
|
||||
if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) ||
|
||||
(DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode)))
|
||||
return; /* no tree diffs in patch format */
|
||||
|
||||
run_diff(p, o);
|
||||
}
|
||||
|
||||
static void diff_flush_stat(struct diff_filepair *p, struct diff_options *o,
|
||||
struct diffstat_t *diffstat)
|
||||
{
|
||||
if (diff_unmodified_pair(p))
|
||||
return;
|
||||
|
||||
if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) ||
|
||||
(DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode)))
|
||||
return; /* no tree diffs in patch format */
|
||||
|
||||
run_diffstat(p, o, diffstat);
|
||||
}
|
||||
|
||||
int diff_queue_is_empty(void)
|
||||
{
|
||||
struct diff_queue_struct *q = &diff_queued_diff;
|
||||
int i;
|
||||
for (i = 0; i < q->nr; i++)
|
||||
if (!diff_unmodified_pair(q->queue[i]))
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
#if DIFF_DEBUG
|
||||
void diff_debug_filespec(struct diff_filespec *s, int x, const char *one)
|
||||
{
|
||||
fprintf(stderr, "queue[%d] %s (%s) %s %06o %s\n",
|
||||
x, one ? one : "",
|
||||
s->path,
|
||||
DIFF_FILE_VALID(s) ? "valid" : "invalid",
|
||||
s->mode,
|
||||
s->sha1_valid ? sha1_to_hex(s->sha1) : "");
|
||||
fprintf(stderr, "queue[%d] %s size %lu flags %d\n",
|
||||
x, one ? one : "",
|
||||
s->size, s->xfrm_flags);
|
||||
}
|
||||
|
||||
void diff_debug_filepair(const struct diff_filepair *p, int i)
|
||||
{
|
||||
diff_debug_filespec(p->one, i, "one");
|
||||
diff_debug_filespec(p->two, i, "two");
|
||||
fprintf(stderr, "score %d, status %c stays %d broken %d\n",
|
||||
p->score, p->status ? p->status : '?',
|
||||
p->source_stays, p->broken_pair);
|
||||
}
|
||||
|
||||
void diff_debug_queue(const char *msg, struct diff_queue_struct *q)
|
||||
{
|
||||
int i;
|
||||
if (msg)
|
||||
fprintf(stderr, "%s\n", msg);
|
||||
fprintf(stderr, "q->nr = %d\n", q->nr);
|
||||
for (i = 0; i < q->nr; i++) {
|
||||
struct diff_filepair *p = q->queue[i];
|
||||
diff_debug_filepair(p, i);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static void diff_resolve_rename_copy(void)
|
||||
{
|
||||
int i, j;
|
||||
struct diff_filepair *p, *pp;
|
||||
struct diff_queue_struct *q = &diff_queued_diff;
|
||||
|
||||
diff_debug_queue("resolve-rename-copy", q);
|
||||
|
||||
for (i = 0; i < q->nr; i++) {
|
||||
p = q->queue[i];
|
||||
p->status = 0; /* undecided */
|
||||
if (DIFF_PAIR_UNMERGED(p))
|
||||
p->status = DIFF_STATUS_UNMERGED;
|
||||
else if (!DIFF_FILE_VALID(p->one))
|
||||
p->status = DIFF_STATUS_ADDED;
|
||||
else if (!DIFF_FILE_VALID(p->two))
|
||||
p->status = DIFF_STATUS_DELETED;
|
||||
else if (DIFF_PAIR_TYPE_CHANGED(p))
|
||||
p->status = DIFF_STATUS_TYPE_CHANGED;
|
||||
|
||||
/* from this point on, we are dealing with a pair
|
||||
* whose both sides are valid and of the same type, i.e.
|
||||
* either in-place edit or rename/copy edit.
|
||||
*/
|
||||
else if (DIFF_PAIR_RENAME(p)) {
|
||||
if (p->source_stays) {
|
||||
p->status = DIFF_STATUS_COPIED;
|
||||
if (revs->combine_merges && num_compare_stages == 2) {
|
||||
show_combined_diff(&combine.p, 2,
|
||||
revs->dense_combined_merges,
|
||||
revs);
|
||||
free(combine.p.path);
|
||||
continue;
|
||||
}
|
||||
/* See if there is some other filepair that
|
||||
* copies from the same source as us. If so
|
||||
* we are a copy. Otherwise we are either a
|
||||
* copy if the path stays, or a rename if it
|
||||
* does not, but we already handled "stays" case.
|
||||
free(combine.p.path);
|
||||
|
||||
/*
|
||||
* Show the diff for the 'ce' if we found the one
|
||||
* from the desired stage.
|
||||
*/
|
||||
for (j = i + 1; j < q->nr; j++) {
|
||||
pp = q->queue[j];
|
||||
if (strcmp(pp->one->path, p->one->path))
|
||||
continue; /* not us */
|
||||
if (!DIFF_PAIR_RENAME(pp))
|
||||
continue; /* not a rename/copy */
|
||||
/* pp is a rename/copy from the same source */
|
||||
p->status = DIFF_STATUS_COPIED;
|
||||
break;
|
||||
diff_unmerge(&revs->diffopt, ce->name);
|
||||
if (ce_stage(ce) != diff_unmerged_stage)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (lstat(ce->name, &st) < 0) {
|
||||
if (errno != ENOENT && errno != ENOTDIR) {
|
||||
perror(ce->name);
|
||||
continue;
|
||||
}
|
||||
if (!p->status)
|
||||
p->status = DIFF_STATUS_RENAMED;
|
||||
}
|
||||
else if (memcmp(p->one->sha1, p->two->sha1, 20) ||
|
||||
p->one->mode != p->two->mode)
|
||||
p->status = DIFF_STATUS_MODIFIED;
|
||||
else {
|
||||
/* This is a "no-change" entry and should not
|
||||
* happen anymore, but prepare for broken callers.
|
||||
*/
|
||||
error("feeding unmodified %s to diffcore",
|
||||
p->one->path);
|
||||
p->status = DIFF_STATUS_UNKNOWN;
|
||||
if (silent_on_removed)
|
||||
continue;
|
||||
diff_addremove(&revs->diffopt, '-', ntohl(ce->ce_mode),
|
||||
ce->sha1, ce->name, NULL);
|
||||
continue;
|
||||
}
|
||||
changed = ce_match_stat(ce, &st, 0);
|
||||
if (!changed && !revs->diffopt.find_copies_harder)
|
||||
continue;
|
||||
oldmode = ntohl(ce->ce_mode);
|
||||
|
||||
newmode = canon_mode(st.st_mode);
|
||||
if (!trust_executable_bit &&
|
||||
S_ISREG(newmode) && S_ISREG(oldmode) &&
|
||||
((newmode ^ oldmode) == 0111))
|
||||
newmode = oldmode;
|
||||
diff_change(&revs->diffopt, oldmode, newmode,
|
||||
ce->sha1, (changed ? null_sha1 : ce->sha1),
|
||||
ce->name, NULL);
|
||||
|
||||
}
|
||||
diff_debug_queue("resolve-rename-copy done", q);
|
||||
diffcore_std(&revs->diffopt);
|
||||
diff_flush(&revs->diffopt);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void flush_one_pair(struct diff_filepair *p,
|
||||
int diff_output_format,
|
||||
struct diff_options *options,
|
||||
struct diffstat_t *diffstat)
|
||||
{
|
||||
int inter_name_termination = '\t';
|
||||
int line_termination = options->line_termination;
|
||||
if (!line_termination)
|
||||
inter_name_termination = 0;
|
||||
/*
|
||||
* diff-index
|
||||
*/
|
||||
|
||||
switch (p->status) {
|
||||
case DIFF_STATUS_UNKNOWN:
|
||||
break;
|
||||
case 0:
|
||||
die("internal error in diff-resolve-rename-copy");
|
||||
break;
|
||||
default:
|
||||
switch (diff_output_format) {
|
||||
case DIFF_FORMAT_DIFFSTAT:
|
||||
diff_flush_stat(p, options, diffstat);
|
||||
break;
|
||||
case DIFF_FORMAT_PATCH:
|
||||
diff_flush_patch(p, options);
|
||||
break;
|
||||
case DIFF_FORMAT_RAW:
|
||||
case DIFF_FORMAT_NAME_STATUS:
|
||||
diff_flush_raw(p, line_termination,
|
||||
inter_name_termination,
|
||||
options, diff_output_format);
|
||||
break;
|
||||
case DIFF_FORMAT_NAME:
|
||||
diff_flush_name(p,
|
||||
inter_name_termination,
|
||||
line_termination);
|
||||
break;
|
||||
case DIFF_FORMAT_NO_OUTPUT:
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* A file entry went away or appeared */
|
||||
static void diff_index_show_file(struct rev_info *revs,
|
||||
const char *prefix,
|
||||
struct cache_entry *ce,
|
||||
unsigned char *sha1, unsigned int mode)
|
||||
{
|
||||
diff_addremove(&revs->diffopt, prefix[0], ntohl(mode),
|
||||
sha1, ce->name, NULL);
|
||||
}
|
||||
|
||||
void diff_flush(struct diff_options *options)
|
||||
static int get_stat_data(struct cache_entry *ce,
|
||||
unsigned char **sha1p,
|
||||
unsigned int *modep,
|
||||
int cached, int match_missing)
|
||||
{
|
||||
struct diff_queue_struct *q = &diff_queued_diff;
|
||||
int i;
|
||||
int diff_output_format = options->output_format;
|
||||
struct diffstat_t *diffstat = NULL;
|
||||
unsigned char *sha1 = ce->sha1;
|
||||
unsigned int mode = ce->ce_mode;
|
||||
|
||||
if (diff_output_format == DIFF_FORMAT_DIFFSTAT || options->with_stat) {
|
||||
diffstat = xcalloc(sizeof (struct diffstat_t), 1);
|
||||
diffstat->xm.consume = diffstat_consume;
|
||||
}
|
||||
|
||||
if (options->with_raw) {
|
||||
for (i = 0; i < q->nr; i++) {
|
||||
struct diff_filepair *p = q->queue[i];
|
||||
flush_one_pair(p, DIFF_FORMAT_RAW, options, NULL);
|
||||
if (!cached) {
|
||||
static unsigned char no_sha1[20];
|
||||
int changed;
|
||||
struct stat st;
|
||||
if (lstat(ce->name, &st) < 0) {
|
||||
if (errno == ENOENT && match_missing) {
|
||||
*sha1p = sha1;
|
||||
*modep = mode;
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
putchar(options->line_termination);
|
||||
}
|
||||
if (options->with_stat) {
|
||||
for (i = 0; i < q->nr; i++) {
|
||||
struct diff_filepair *p = q->queue[i];
|
||||
flush_one_pair(p, DIFF_FORMAT_DIFFSTAT, options,
|
||||
diffstat);
|
||||
changed = ce_match_stat(ce, &st, 0);
|
||||
if (changed) {
|
||||
mode = create_ce_mode(st.st_mode);
|
||||
if (!trust_executable_bit && S_ISREG(st.st_mode))
|
||||
mode = ce->ce_mode;
|
||||
sha1 = no_sha1;
|
||||
}
|
||||
show_stats(diffstat);
|
||||
free(diffstat);
|
||||
diffstat = NULL;
|
||||
putchar(options->line_termination);
|
||||
}
|
||||
for (i = 0; i < q->nr; i++) {
|
||||
struct diff_filepair *p = q->queue[i];
|
||||
flush_one_pair(p, diff_output_format, options, diffstat);
|
||||
diff_free_filepair(p);
|
||||
}
|
||||
|
||||
if (diffstat) {
|
||||
show_stats(diffstat);
|
||||
free(diffstat);
|
||||
}
|
||||
|
||||
free(q->queue);
|
||||
q->queue = NULL;
|
||||
q->nr = q->alloc = 0;
|
||||
*sha1p = sha1;
|
||||
*modep = mode;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void diffcore_apply_filter(const char *filter)
|
||||
static void show_new_file(struct rev_info *revs,
|
||||
struct cache_entry *new,
|
||||
int cached, int match_missing)
|
||||
{
|
||||
int i;
|
||||
struct diff_queue_struct *q = &diff_queued_diff;
|
||||
struct diff_queue_struct outq;
|
||||
outq.queue = NULL;
|
||||
outq.nr = outq.alloc = 0;
|
||||
unsigned char *sha1;
|
||||
unsigned int mode;
|
||||
|
||||
if (!filter)
|
||||
/* New file in the index: it might actually be different in
|
||||
* the working copy.
|
||||
*/
|
||||
if (get_stat_data(new, &sha1, &mode, cached, match_missing) < 0)
|
||||
return;
|
||||
|
||||
if (strchr(filter, DIFF_STATUS_FILTER_AON)) {
|
||||
int found;
|
||||
for (i = found = 0; !found && i < q->nr; i++) {
|
||||
struct diff_filepair *p = q->queue[i];
|
||||
if (((p->status == DIFF_STATUS_MODIFIED) &&
|
||||
((p->score &&
|
||||
strchr(filter, DIFF_STATUS_FILTER_BROKEN)) ||
|
||||
(!p->score &&
|
||||
strchr(filter, DIFF_STATUS_MODIFIED)))) ||
|
||||
((p->status != DIFF_STATUS_MODIFIED) &&
|
||||
strchr(filter, p->status)))
|
||||
found++;
|
||||
}
|
||||
if (found)
|
||||
return;
|
||||
diff_index_show_file(revs, "+", new, sha1, mode);
|
||||
}
|
||||
|
||||
/* otherwise we will clear the whole queue
|
||||
* by copying the empty outq at the end of this
|
||||
* function, but first clear the current entries
|
||||
* in the queue.
|
||||
static int show_modified(struct rev_info *revs,
|
||||
struct cache_entry *old,
|
||||
struct cache_entry *new,
|
||||
int report_missing,
|
||||
int cached, int match_missing)
|
||||
{
|
||||
unsigned int mode, oldmode;
|
||||
unsigned char *sha1;
|
||||
|
||||
if (get_stat_data(new, &sha1, &mode, cached, match_missing) < 0) {
|
||||
if (report_missing)
|
||||
diff_index_show_file(revs, "-", old,
|
||||
old->sha1, old->ce_mode);
|
||||
return -1;
|
||||
}
|
||||
|
||||
oldmode = old->ce_mode;
|
||||
if (mode == oldmode && !memcmp(sha1, old->sha1, 20) &&
|
||||
!revs->diffopt.find_copies_harder)
|
||||
return 0;
|
||||
|
||||
mode = ntohl(mode);
|
||||
oldmode = ntohl(oldmode);
|
||||
|
||||
diff_change(&revs->diffopt, oldmode, mode,
|
||||
old->sha1, sha1, old->name, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int diff_cache(struct rev_info *revs,
|
||||
struct cache_entry **ac, int entries,
|
||||
const char **pathspec,
|
||||
int cached, int match_missing)
|
||||
{
|
||||
while (entries) {
|
||||
struct cache_entry *ce = *ac;
|
||||
int same = (entries > 1) && ce_same_name(ce, ac[1]);
|
||||
|
||||
if (!ce_path_match(ce, pathspec))
|
||||
goto skip_entry;
|
||||
|
||||
switch (ce_stage(ce)) {
|
||||
case 0:
|
||||
/* No stage 1 entry? That means it's a new file */
|
||||
if (!same) {
|
||||
show_new_file(revs, ce, cached, match_missing);
|
||||
break;
|
||||
}
|
||||
/* Show difference between old and new */
|
||||
show_modified(revs,ac[1], ce, 1,
|
||||
cached, match_missing);
|
||||
break;
|
||||
case 1:
|
||||
/* No stage 3 (merge) entry?
|
||||
* That means it's been deleted.
|
||||
*/
|
||||
if (!same) {
|
||||
diff_index_show_file(revs, "-", ce,
|
||||
ce->sha1, ce->ce_mode);
|
||||
break;
|
||||
}
|
||||
/* We come here with ce pointing at stage 1
|
||||
* (original tree) and ac[1] pointing at stage
|
||||
* 3 (unmerged). show-modified with
|
||||
* report-missing set to false does not say the
|
||||
* file is deleted but reports true if work
|
||||
* tree does not have it, in which case we
|
||||
* fall through to report the unmerged state.
|
||||
* Otherwise, we show the differences between
|
||||
* the original tree and the work tree.
|
||||
*/
|
||||
if (!cached &&
|
||||
!show_modified(revs, ce, ac[1], 0,
|
||||
cached, match_missing))
|
||||
break;
|
||||
/* fallthru */
|
||||
case 3:
|
||||
diff_unmerge(&revs->diffopt, ce->name);
|
||||
break;
|
||||
|
||||
default:
|
||||
die("impossible cache entry stage");
|
||||
}
|
||||
|
||||
skip_entry:
|
||||
/*
|
||||
* Ignore all the different stages for this file,
|
||||
* we've handled the relevant cases now.
|
||||
*/
|
||||
for (i = 0; i < q->nr; i++)
|
||||
diff_free_filepair(q->queue[i]);
|
||||
do {
|
||||
ac++;
|
||||
entries--;
|
||||
} while (entries && ce_same_name(ce, ac[0]));
|
||||
}
|
||||
else {
|
||||
/* Only the matching ones */
|
||||
for (i = 0; i < q->nr; i++) {
|
||||
struct diff_filepair *p = q->queue[i];
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (((p->status == DIFF_STATUS_MODIFIED) &&
|
||||
((p->score &&
|
||||
strchr(filter, DIFF_STATUS_FILTER_BROKEN)) ||
|
||||
(!p->score &&
|
||||
strchr(filter, DIFF_STATUS_MODIFIED)))) ||
|
||||
((p->status != DIFF_STATUS_MODIFIED) &&
|
||||
strchr(filter, p->status)))
|
||||
diff_q(&outq, p);
|
||||
else
|
||||
diff_free_filepair(p);
|
||||
}
|
||||
/*
|
||||
* This turns all merge entries into "stage 3". That guarantees that
|
||||
* when we read in the new tree (into "stage 1"), we won't lose sight
|
||||
* of the fact that we had unmerged entries.
|
||||
*/
|
||||
static void mark_merge_entries(void)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < active_nr; i++) {
|
||||
struct cache_entry *ce = active_cache[i];
|
||||
if (!ce_stage(ce))
|
||||
continue;
|
||||
ce->ce_flags |= htons(CE_STAGEMASK);
|
||||
}
|
||||
free(q->queue);
|
||||
*q = outq;
|
||||
}
|
||||
|
||||
void diffcore_std(struct diff_options *options)
|
||||
int run_diff_index(struct rev_info *revs, int cached)
|
||||
{
|
||||
if (options->break_opt != -1)
|
||||
diffcore_break(options->break_opt);
|
||||
if (options->detect_rename)
|
||||
diffcore_rename(options);
|
||||
if (options->break_opt != -1)
|
||||
diffcore_merge_broken();
|
||||
if (options->pickaxe)
|
||||
diffcore_pickaxe(options->pickaxe, options->pickaxe_opts);
|
||||
if (options->orderfile)
|
||||
diffcore_order(options->orderfile);
|
||||
diff_resolve_rename_copy();
|
||||
diffcore_apply_filter(options->filter);
|
||||
}
|
||||
int ret;
|
||||
struct object *ent;
|
||||
struct tree *tree;
|
||||
const char *tree_name;
|
||||
int match_missing = 0;
|
||||
|
||||
|
||||
void diffcore_std_no_resolve(struct diff_options *options)
|
||||
{
|
||||
if (options->pickaxe)
|
||||
diffcore_pickaxe(options->pickaxe, options->pickaxe_opts);
|
||||
if (options->orderfile)
|
||||
diffcore_order(options->orderfile);
|
||||
diffcore_apply_filter(options->filter);
|
||||
}
|
||||
|
||||
void diff_addremove(struct diff_options *options,
|
||||
int addremove, unsigned mode,
|
||||
const unsigned char *sha1,
|
||||
const char *base, const char *path)
|
||||
{
|
||||
char concatpath[PATH_MAX];
|
||||
struct diff_filespec *one, *two;
|
||||
|
||||
/* This may look odd, but it is a preparation for
|
||||
* feeding "there are unchanged files which should
|
||||
* not produce diffs, but when you are doing copy
|
||||
* detection you would need them, so here they are"
|
||||
* entries to the diff-core. They will be prefixed
|
||||
* with something like '=' or '*' (I haven't decided
|
||||
* which but should not make any difference).
|
||||
* Feeding the same new and old to diff_change()
|
||||
* also has the same effect.
|
||||
* Before the final output happens, they are pruned after
|
||||
* merged into rename/copy pairs as appropriate.
|
||||
/*
|
||||
* Backward compatibility wart - "diff-index -m" does
|
||||
* not mean "do not ignore merges", but totally different.
|
||||
*/
|
||||
if (options->reverse_diff)
|
||||
addremove = (addremove == '+' ? '-' :
|
||||
addremove == '-' ? '+' : addremove);
|
||||
if (!revs->ignore_merges)
|
||||
match_missing = 1;
|
||||
|
||||
if (!path) path = "";
|
||||
sprintf(concatpath, "%s%s", base, path);
|
||||
one = alloc_filespec(concatpath);
|
||||
two = alloc_filespec(concatpath);
|
||||
|
||||
if (addremove != '+')
|
||||
fill_filespec(one, sha1, mode);
|
||||
if (addremove != '-')
|
||||
fill_filespec(two, sha1, mode);
|
||||
|
||||
diff_queue(&diff_queued_diff, one, two);
|
||||
}
|
||||
|
||||
void diff_change(struct diff_options *options,
|
||||
unsigned old_mode, unsigned new_mode,
|
||||
const unsigned char *old_sha1,
|
||||
const unsigned char *new_sha1,
|
||||
const char *base, const char *path)
|
||||
{
|
||||
char concatpath[PATH_MAX];
|
||||
struct diff_filespec *one, *two;
|
||||
|
||||
if (options->reverse_diff) {
|
||||
unsigned tmp;
|
||||
const unsigned char *tmp_c;
|
||||
tmp = old_mode; old_mode = new_mode; new_mode = tmp;
|
||||
tmp_c = old_sha1; old_sha1 = new_sha1; new_sha1 = tmp_c;
|
||||
if (read_cache() < 0) {
|
||||
perror("read_cache");
|
||||
return -1;
|
||||
}
|
||||
if (!path) path = "";
|
||||
sprintf(concatpath, "%s%s", base, path);
|
||||
one = alloc_filespec(concatpath);
|
||||
two = alloc_filespec(concatpath);
|
||||
fill_filespec(one, old_sha1, old_mode);
|
||||
fill_filespec(two, new_sha1, new_mode);
|
||||
mark_merge_entries();
|
||||
|
||||
diff_queue(&diff_queued_diff, one, two);
|
||||
}
|
||||
|
||||
void diff_unmerge(struct diff_options *options,
|
||||
const char *path)
|
||||
{
|
||||
struct diff_filespec *one, *two;
|
||||
one = alloc_filespec(path);
|
||||
two = alloc_filespec(path);
|
||||
diff_queue(&diff_queued_diff, one, two);
|
||||
ent = revs->pending_objects->item;
|
||||
tree_name = revs->pending_objects->name;
|
||||
tree = parse_tree_indirect(ent->sha1);
|
||||
if (!tree)
|
||||
return error("bad tree object %s", tree_name);
|
||||
if (read_tree(tree, 1, revs->prune_data))
|
||||
return error("unable to read tree object %s", tree_name);
|
||||
ret = diff_cache(revs, active_cache, active_nr, revs->prune_data,
|
||||
cached, match_missing);
|
||||
diffcore_std(&revs->diffopt);
|
||||
diff_flush(&revs->diffopt);
|
||||
return ret;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,1803 @@
|
|||
/*
|
||||
* Copyright (C) 2005 Junio C Hamano
|
||||
*/
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <signal.h>
|
||||
#include "cache.h"
|
||||
#include "quote.h"
|
||||
#include "diff.h"
|
||||
#include "diffcore.h"
|
||||
#include "xdiff-interface.h"
|
||||
|
||||
static int use_size_cache;
|
||||
|
||||
int diff_rename_limit_default = -1;
|
||||
|
||||
int git_diff_config(const char *var, const char *value)
|
||||
{
|
||||
if (!strcmp(var, "diff.renamelimit")) {
|
||||
diff_rename_limit_default = git_config_int(var, value);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return git_default_config(var, value);
|
||||
}
|
||||
|
||||
static char *quote_one(const char *str)
|
||||
{
|
||||
int needlen;
|
||||
char *xp;
|
||||
|
||||
if (!str)
|
||||
return NULL;
|
||||
needlen = quote_c_style(str, NULL, NULL, 0);
|
||||
if (!needlen)
|
||||
return strdup(str);
|
||||
xp = xmalloc(needlen + 1);
|
||||
quote_c_style(str, xp, NULL, 0);
|
||||
return xp;
|
||||
}
|
||||
|
||||
static char *quote_two(const char *one, const char *two)
|
||||
{
|
||||
int need_one = quote_c_style(one, NULL, NULL, 1);
|
||||
int need_two = quote_c_style(two, NULL, NULL, 1);
|
||||
char *xp;
|
||||
|
||||
if (need_one + need_two) {
|
||||
if (!need_one) need_one = strlen(one);
|
||||
if (!need_two) need_one = strlen(two);
|
||||
|
||||
xp = xmalloc(need_one + need_two + 3);
|
||||
xp[0] = '"';
|
||||
quote_c_style(one, xp + 1, NULL, 1);
|
||||
quote_c_style(two, xp + need_one + 1, NULL, 1);
|
||||
strcpy(xp + need_one + need_two + 1, "\"");
|
||||
return xp;
|
||||
}
|
||||
need_one = strlen(one);
|
||||
need_two = strlen(two);
|
||||
xp = xmalloc(need_one + need_two + 1);
|
||||
strcpy(xp, one);
|
||||
strcpy(xp + need_one, two);
|
||||
return xp;
|
||||
}
|
||||
|
||||
static const char *external_diff(void)
|
||||
{
|
||||
static const char *external_diff_cmd = NULL;
|
||||
static int done_preparing = 0;
|
||||
|
||||
if (done_preparing)
|
||||
return external_diff_cmd;
|
||||
external_diff_cmd = getenv("GIT_EXTERNAL_DIFF");
|
||||
done_preparing = 1;
|
||||
return external_diff_cmd;
|
||||
}
|
||||
|
||||
#define TEMPFILE_PATH_LEN 50
|
||||
|
||||
static struct diff_tempfile {
|
||||
const char *name; /* filename external diff should read from */
|
||||
char hex[41];
|
||||
char mode[10];
|
||||
char tmp_path[TEMPFILE_PATH_LEN];
|
||||
} diff_temp[2];
|
||||
|
||||
static int count_lines(const char *data, int size)
|
||||
{
|
||||
int count, ch, completely_empty = 1, nl_just_seen = 0;
|
||||
count = 0;
|
||||
while (0 < size--) {
|
||||
ch = *data++;
|
||||
if (ch == '\n') {
|
||||
count++;
|
||||
nl_just_seen = 1;
|
||||
completely_empty = 0;
|
||||
}
|
||||
else {
|
||||
nl_just_seen = 0;
|
||||
completely_empty = 0;
|
||||
}
|
||||
}
|
||||
if (completely_empty)
|
||||
return 0;
|
||||
if (!nl_just_seen)
|
||||
count++; /* no trailing newline */
|
||||
return count;
|
||||
}
|
||||
|
||||
static void print_line_count(int count)
|
||||
{
|
||||
switch (count) {
|
||||
case 0:
|
||||
printf("0,0");
|
||||
break;
|
||||
case 1:
|
||||
printf("1");
|
||||
break;
|
||||
default:
|
||||
printf("1,%d", count);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void copy_file(int prefix, const char *data, int size)
|
||||
{
|
||||
int ch, nl_just_seen = 1;
|
||||
while (0 < size--) {
|
||||
ch = *data++;
|
||||
if (nl_just_seen)
|
||||
putchar(prefix);
|
||||
putchar(ch);
|
||||
if (ch == '\n')
|
||||
nl_just_seen = 1;
|
||||
else
|
||||
nl_just_seen = 0;
|
||||
}
|
||||
if (!nl_just_seen)
|
||||
printf("\n\\ No newline at end of file\n");
|
||||
}
|
||||
|
||||
static void emit_rewrite_diff(const char *name_a,
|
||||
const char *name_b,
|
||||
struct diff_filespec *one,
|
||||
struct diff_filespec *two)
|
||||
{
|
||||
int lc_a, lc_b;
|
||||
diff_populate_filespec(one, 0);
|
||||
diff_populate_filespec(two, 0);
|
||||
lc_a = count_lines(one->data, one->size);
|
||||
lc_b = count_lines(two->data, two->size);
|
||||
printf("--- %s\n+++ %s\n@@ -", name_a, name_b);
|
||||
print_line_count(lc_a);
|
||||
printf(" +");
|
||||
print_line_count(lc_b);
|
||||
printf(" @@\n");
|
||||
if (lc_a)
|
||||
copy_file('-', one->data, one->size);
|
||||
if (lc_b)
|
||||
copy_file('+', two->data, two->size);
|
||||
}
|
||||
|
||||
static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one)
|
||||
{
|
||||
if (!DIFF_FILE_VALID(one)) {
|
||||
mf->ptr = ""; /* does not matter */
|
||||
mf->size = 0;
|
||||
return 0;
|
||||
}
|
||||
else if (diff_populate_filespec(one, 0))
|
||||
return -1;
|
||||
mf->ptr = one->data;
|
||||
mf->size = one->size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct emit_callback {
|
||||
const char **label_path;
|
||||
};
|
||||
|
||||
static int fn_out(void *priv, mmbuffer_t *mb, int nbuf)
|
||||
{
|
||||
int i;
|
||||
struct emit_callback *ecbdata = priv;
|
||||
|
||||
if (ecbdata->label_path[0]) {
|
||||
printf("--- %s\n", ecbdata->label_path[0]);
|
||||
printf("+++ %s\n", ecbdata->label_path[1]);
|
||||
ecbdata->label_path[0] = ecbdata->label_path[1] = NULL;
|
||||
}
|
||||
for (i = 0; i < nbuf; i++)
|
||||
if (!fwrite(mb[i].ptr, mb[i].size, 1, stdout))
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char *pprint_rename(const char *a, const char *b)
|
||||
{
|
||||
const char *old = a;
|
||||
const char *new = b;
|
||||
char *name = NULL;
|
||||
int pfx_length, sfx_length;
|
||||
int len_a = strlen(a);
|
||||
int len_b = strlen(b);
|
||||
|
||||
/* Find common prefix */
|
||||
pfx_length = 0;
|
||||
while (*old && *new && *old == *new) {
|
||||
if (*old == '/')
|
||||
pfx_length = old - a + 1;
|
||||
old++;
|
||||
new++;
|
||||
}
|
||||
|
||||
/* Find common suffix */
|
||||
old = a + len_a;
|
||||
new = b + len_b;
|
||||
sfx_length = 0;
|
||||
while (a <= old && b <= new && *old == *new) {
|
||||
if (*old == '/')
|
||||
sfx_length = len_a - (old - a);
|
||||
old--;
|
||||
new--;
|
||||
}
|
||||
|
||||
/*
|
||||
* pfx{mid-a => mid-b}sfx
|
||||
* {pfx-a => pfx-b}sfx
|
||||
* pfx{sfx-a => sfx-b}
|
||||
* name-a => name-b
|
||||
*/
|
||||
if (pfx_length + sfx_length) {
|
||||
name = xmalloc(len_a + len_b - pfx_length - sfx_length + 7);
|
||||
sprintf(name, "%.*s{%.*s => %.*s}%s",
|
||||
pfx_length, a,
|
||||
len_a - pfx_length - sfx_length, a + pfx_length,
|
||||
len_b - pfx_length - sfx_length, b + pfx_length,
|
||||
a + len_a - sfx_length);
|
||||
}
|
||||
else {
|
||||
name = xmalloc(len_a + len_b + 5);
|
||||
sprintf(name, "%s => %s", a, b);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
struct diffstat_t {
|
||||
struct xdiff_emit_state xm;
|
||||
|
||||
int nr;
|
||||
int alloc;
|
||||
struct diffstat_file {
|
||||
char *name;
|
||||
unsigned is_unmerged:1;
|
||||
unsigned is_binary:1;
|
||||
unsigned is_renamed:1;
|
||||
unsigned int added, deleted;
|
||||
} **files;
|
||||
};
|
||||
|
||||
static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat,
|
||||
const char *name_a,
|
||||
const char *name_b)
|
||||
{
|
||||
struct diffstat_file *x;
|
||||
x = xcalloc(sizeof (*x), 1);
|
||||
if (diffstat->nr == diffstat->alloc) {
|
||||
diffstat->alloc = alloc_nr(diffstat->alloc);
|
||||
diffstat->files = xrealloc(diffstat->files,
|
||||
diffstat->alloc * sizeof(x));
|
||||
}
|
||||
diffstat->files[diffstat->nr++] = x;
|
||||
if (name_b) {
|
||||
x->name = pprint_rename(name_a, name_b);
|
||||
x->is_renamed = 1;
|
||||
}
|
||||
else
|
||||
x->name = strdup(name_a);
|
||||
return x;
|
||||
}
|
||||
|
||||
static void diffstat_consume(void *priv, char *line, unsigned long len)
|
||||
{
|
||||
struct diffstat_t *diffstat = priv;
|
||||
struct diffstat_file *x = diffstat->files[diffstat->nr - 1];
|
||||
|
||||
if (line[0] == '+')
|
||||
x->added++;
|
||||
else if (line[0] == '-')
|
||||
x->deleted++;
|
||||
}
|
||||
|
||||
static const char pluses[] = "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++";
|
||||
static const char minuses[]= "----------------------------------------------------------------------";
|
||||
|
||||
static void show_stats(struct diffstat_t* data)
|
||||
{
|
||||
char *prefix = "";
|
||||
int i, len, add, del, total, adds = 0, dels = 0;
|
||||
int max, max_change = 0, max_len = 0;
|
||||
int total_files = data->nr;
|
||||
|
||||
if (data->nr == 0)
|
||||
return;
|
||||
|
||||
for (i = 0; i < data->nr; i++) {
|
||||
struct diffstat_file *file = data->files[i];
|
||||
|
||||
len = strlen(file->name);
|
||||
if (max_len < len)
|
||||
max_len = len;
|
||||
|
||||
if (file->is_binary || file->is_unmerged)
|
||||
continue;
|
||||
if (max_change < file->added + file->deleted)
|
||||
max_change = file->added + file->deleted;
|
||||
}
|
||||
|
||||
for (i = 0; i < data->nr; i++) {
|
||||
char *name = data->files[i]->name;
|
||||
int added = data->files[i]->added;
|
||||
int deleted = data->files[i]->deleted;
|
||||
|
||||
if (0 < (len = quote_c_style(name, NULL, NULL, 0))) {
|
||||
char *qname = xmalloc(len + 1);
|
||||
quote_c_style(name, qname, NULL, 0);
|
||||
free(name);
|
||||
data->files[i]->name = name = qname;
|
||||
}
|
||||
|
||||
/*
|
||||
* "scale" the filename
|
||||
*/
|
||||
len = strlen(name);
|
||||
max = max_len;
|
||||
if (max > 50)
|
||||
max = 50;
|
||||
if (len > max) {
|
||||
char *slash;
|
||||
prefix = "...";
|
||||
max -= 3;
|
||||
name += len - max;
|
||||
slash = strchr(name, '/');
|
||||
if (slash)
|
||||
name = slash;
|
||||
}
|
||||
len = max;
|
||||
|
||||
/*
|
||||
* scale the add/delete
|
||||
*/
|
||||
max = max_change;
|
||||
if (max + len > 70)
|
||||
max = 70 - len;
|
||||
|
||||
if (data->files[i]->is_binary) {
|
||||
printf(" %s%-*s | Bin\n", prefix, len, name);
|
||||
goto free_diffstat_file;
|
||||
}
|
||||
else if (data->files[i]->is_unmerged) {
|
||||
printf(" %s%-*s | Unmerged\n", prefix, len, name);
|
||||
goto free_diffstat_file;
|
||||
}
|
||||
else if (!data->files[i]->is_renamed &&
|
||||
(added + deleted == 0)) {
|
||||
total_files--;
|
||||
goto free_diffstat_file;
|
||||
}
|
||||
|
||||
add = added;
|
||||
del = deleted;
|
||||
total = add + del;
|
||||
adds += add;
|
||||
dels += del;
|
||||
|
||||
if (max_change > 0) {
|
||||
total = (total * max + max_change / 2) / max_change;
|
||||
add = (add * max + max_change / 2) / max_change;
|
||||
del = total - add;
|
||||
}
|
||||
printf(" %s%-*s |%5d %.*s%.*s\n", prefix,
|
||||
len, name, added + deleted,
|
||||
add, pluses, del, minuses);
|
||||
free_diffstat_file:
|
||||
free(data->files[i]->name);
|
||||
free(data->files[i]);
|
||||
}
|
||||
free(data->files);
|
||||
printf(" %d files changed, %d insertions(+), %d deletions(-)\n",
|
||||
total_files, adds, dels);
|
||||
}
|
||||
|
||||
#define FIRST_FEW_BYTES 8000
|
||||
static int mmfile_is_binary(mmfile_t *mf)
|
||||
{
|
||||
long sz = mf->size;
|
||||
if (FIRST_FEW_BYTES < sz)
|
||||
sz = FIRST_FEW_BYTES;
|
||||
if (memchr(mf->ptr, 0, sz))
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void builtin_diff(const char *name_a,
|
||||
const char *name_b,
|
||||
struct diff_filespec *one,
|
||||
struct diff_filespec *two,
|
||||
const char *xfrm_msg,
|
||||
int complete_rewrite)
|
||||
{
|
||||
mmfile_t mf1, mf2;
|
||||
const char *lbl[2];
|
||||
char *a_one, *b_two;
|
||||
|
||||
a_one = quote_two("a/", name_a);
|
||||
b_two = quote_two("b/", name_b);
|
||||
lbl[0] = DIFF_FILE_VALID(one) ? a_one : "/dev/null";
|
||||
lbl[1] = DIFF_FILE_VALID(two) ? b_two : "/dev/null";
|
||||
printf("diff --git %s %s\n", a_one, b_two);
|
||||
if (lbl[0][0] == '/') {
|
||||
/* /dev/null */
|
||||
printf("new file mode %06o\n", two->mode);
|
||||
if (xfrm_msg && xfrm_msg[0])
|
||||
puts(xfrm_msg);
|
||||
}
|
||||
else if (lbl[1][0] == '/') {
|
||||
printf("deleted file mode %06o\n", one->mode);
|
||||
if (xfrm_msg && xfrm_msg[0])
|
||||
puts(xfrm_msg);
|
||||
}
|
||||
else {
|
||||
if (one->mode != two->mode) {
|
||||
printf("old mode %06o\n", one->mode);
|
||||
printf("new mode %06o\n", two->mode);
|
||||
}
|
||||
if (xfrm_msg && xfrm_msg[0])
|
||||
puts(xfrm_msg);
|
||||
/*
|
||||
* we do not run diff between different kind
|
||||
* of objects.
|
||||
*/
|
||||
if ((one->mode ^ two->mode) & S_IFMT)
|
||||
goto free_ab_and_return;
|
||||
if (complete_rewrite) {
|
||||
emit_rewrite_diff(name_a, name_b, one, two);
|
||||
goto free_ab_and_return;
|
||||
}
|
||||
}
|
||||
|
||||
if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
|
||||
die("unable to read files to diff");
|
||||
|
||||
if (mmfile_is_binary(&mf1) || mmfile_is_binary(&mf2))
|
||||
printf("Binary files %s and %s differ\n", lbl[0], lbl[1]);
|
||||
else {
|
||||
/* Crazy xdl interfaces.. */
|
||||
const char *diffopts = getenv("GIT_DIFF_OPTS");
|
||||
xpparam_t xpp;
|
||||
xdemitconf_t xecfg;
|
||||
xdemitcb_t ecb;
|
||||
struct emit_callback ecbdata;
|
||||
|
||||
ecbdata.label_path = lbl;
|
||||
xpp.flags = XDF_NEED_MINIMAL;
|
||||
xecfg.ctxlen = 3;
|
||||
xecfg.flags = XDL_EMIT_FUNCNAMES;
|
||||
if (!diffopts)
|
||||
;
|
||||
else if (!strncmp(diffopts, "--unified=", 10))
|
||||
xecfg.ctxlen = strtoul(diffopts + 10, NULL, 10);
|
||||
else if (!strncmp(diffopts, "-u", 2))
|
||||
xecfg.ctxlen = strtoul(diffopts + 2, NULL, 10);
|
||||
ecb.outf = fn_out;
|
||||
ecb.priv = &ecbdata;
|
||||
xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
|
||||
}
|
||||
|
||||
free_ab_and_return:
|
||||
free(a_one);
|
||||
free(b_two);
|
||||
return;
|
||||
}
|
||||
|
||||
static void builtin_diffstat(const char *name_a, const char *name_b,
|
||||
struct diff_filespec *one,
|
||||
struct diff_filespec *two,
|
||||
struct diffstat_t *diffstat,
|
||||
int complete_rewrite)
|
||||
{
|
||||
mmfile_t mf1, mf2;
|
||||
struct diffstat_file *data;
|
||||
|
||||
data = diffstat_add(diffstat, name_a, name_b);
|
||||
|
||||
if (!one || !two) {
|
||||
data->is_unmerged = 1;
|
||||
return;
|
||||
}
|
||||
if (complete_rewrite) {
|
||||
diff_populate_filespec(one, 0);
|
||||
diff_populate_filespec(two, 0);
|
||||
data->deleted = count_lines(one->data, one->size);
|
||||
data->added = count_lines(two->data, two->size);
|
||||
return;
|
||||
}
|
||||
if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
|
||||
die("unable to read files to diff");
|
||||
|
||||
if (mmfile_is_binary(&mf1) || mmfile_is_binary(&mf2))
|
||||
data->is_binary = 1;
|
||||
else {
|
||||
/* Crazy xdl interfaces.. */
|
||||
xpparam_t xpp;
|
||||
xdemitconf_t xecfg;
|
||||
xdemitcb_t ecb;
|
||||
|
||||
xpp.flags = XDF_NEED_MINIMAL;
|
||||
xecfg.ctxlen = 0;
|
||||
xecfg.flags = 0;
|
||||
ecb.outf = xdiff_outf;
|
||||
ecb.priv = diffstat;
|
||||
xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
|
||||
}
|
||||
}
|
||||
|
||||
struct diff_filespec *alloc_filespec(const char *path)
|
||||
{
|
||||
int namelen = strlen(path);
|
||||
struct diff_filespec *spec = xmalloc(sizeof(*spec) + namelen + 1);
|
||||
|
||||
memset(spec, 0, sizeof(*spec));
|
||||
spec->path = (char *)(spec + 1);
|
||||
memcpy(spec->path, path, namelen+1);
|
||||
return spec;
|
||||
}
|
||||
|
||||
void fill_filespec(struct diff_filespec *spec, const unsigned char *sha1,
|
||||
unsigned short mode)
|
||||
{
|
||||
if (mode) {
|
||||
spec->mode = canon_mode(mode);
|
||||
memcpy(spec->sha1, sha1, 20);
|
||||
spec->sha1_valid = !!memcmp(sha1, null_sha1, 20);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Given a name and sha1 pair, if the dircache tells us the file in
|
||||
* the work tree has that object contents, return true, so that
|
||||
* prepare_temp_file() does not have to inflate and extract.
|
||||
*/
|
||||
static int work_tree_matches(const char *name, const unsigned char *sha1)
|
||||
{
|
||||
struct cache_entry *ce;
|
||||
struct stat st;
|
||||
int pos, len;
|
||||
|
||||
/* We do not read the cache ourselves here, because the
|
||||
* benchmark with my previous version that always reads cache
|
||||
* shows that it makes things worse for diff-tree comparing
|
||||
* two linux-2.6 kernel trees in an already checked out work
|
||||
* tree. This is because most diff-tree comparisons deal with
|
||||
* only a small number of files, while reading the cache is
|
||||
* expensive for a large project, and its cost outweighs the
|
||||
* savings we get by not inflating the object to a temporary
|
||||
* file. Practically, this code only helps when we are used
|
||||
* by diff-cache --cached, which does read the cache before
|
||||
* calling us.
|
||||
*/
|
||||
if (!active_cache)
|
||||
return 0;
|
||||
|
||||
len = strlen(name);
|
||||
pos = cache_name_pos(name, len);
|
||||
if (pos < 0)
|
||||
return 0;
|
||||
ce = active_cache[pos];
|
||||
if ((lstat(name, &st) < 0) ||
|
||||
!S_ISREG(st.st_mode) || /* careful! */
|
||||
ce_match_stat(ce, &st, 0) ||
|
||||
memcmp(sha1, ce->sha1, 20))
|
||||
return 0;
|
||||
/* we return 1 only when we can stat, it is a regular file,
|
||||
* stat information matches, and sha1 recorded in the cache
|
||||
* matches. I.e. we know the file in the work tree really is
|
||||
* the same as the <name, sha1> pair.
|
||||
*/
|
||||
return 1;
|
||||
}
|
||||
|
||||
static struct sha1_size_cache {
|
||||
unsigned char sha1[20];
|
||||
unsigned long size;
|
||||
} **sha1_size_cache;
|
||||
static int sha1_size_cache_nr, sha1_size_cache_alloc;
|
||||
|
||||
static struct sha1_size_cache *locate_size_cache(unsigned char *sha1,
|
||||
int find_only,
|
||||
unsigned long size)
|
||||
{
|
||||
int first, last;
|
||||
struct sha1_size_cache *e;
|
||||
|
||||
first = 0;
|
||||
last = sha1_size_cache_nr;
|
||||
while (last > first) {
|
||||
int cmp, next = (last + first) >> 1;
|
||||
e = sha1_size_cache[next];
|
||||
cmp = memcmp(e->sha1, sha1, 20);
|
||||
if (!cmp)
|
||||
return e;
|
||||
if (cmp < 0) {
|
||||
last = next;
|
||||
continue;
|
||||
}
|
||||
first = next+1;
|
||||
}
|
||||
/* not found */
|
||||
if (find_only)
|
||||
return NULL;
|
||||
/* insert to make it at "first" */
|
||||
if (sha1_size_cache_alloc <= sha1_size_cache_nr) {
|
||||
sha1_size_cache_alloc = alloc_nr(sha1_size_cache_alloc);
|
||||
sha1_size_cache = xrealloc(sha1_size_cache,
|
||||
sha1_size_cache_alloc *
|
||||
sizeof(*sha1_size_cache));
|
||||
}
|
||||
sha1_size_cache_nr++;
|
||||
if (first < sha1_size_cache_nr)
|
||||
memmove(sha1_size_cache + first + 1, sha1_size_cache + first,
|
||||
(sha1_size_cache_nr - first - 1) *
|
||||
sizeof(*sha1_size_cache));
|
||||
e = xmalloc(sizeof(struct sha1_size_cache));
|
||||
sha1_size_cache[first] = e;
|
||||
memcpy(e->sha1, sha1, 20);
|
||||
e->size = size;
|
||||
return e;
|
||||
}
|
||||
|
||||
/*
|
||||
* While doing rename detection and pickaxe operation, we may need to
|
||||
* grab the data for the blob (or file) for our own in-core comparison.
|
||||
* diff_filespec has data and size fields for this purpose.
|
||||
*/
|
||||
int diff_populate_filespec(struct diff_filespec *s, int size_only)
|
||||
{
|
||||
int err = 0;
|
||||
if (!DIFF_FILE_VALID(s))
|
||||
die("internal error: asking to populate invalid file.");
|
||||
if (S_ISDIR(s->mode))
|
||||
return -1;
|
||||
|
||||
if (!use_size_cache)
|
||||
size_only = 0;
|
||||
|
||||
if (s->data)
|
||||
return err;
|
||||
if (!s->sha1_valid ||
|
||||
work_tree_matches(s->path, s->sha1)) {
|
||||
struct stat st;
|
||||
int fd;
|
||||
if (lstat(s->path, &st) < 0) {
|
||||
if (errno == ENOENT) {
|
||||
err_empty:
|
||||
err = -1;
|
||||
empty:
|
||||
s->data = "";
|
||||
s->size = 0;
|
||||
return err;
|
||||
}
|
||||
}
|
||||
s->size = st.st_size;
|
||||
if (!s->size)
|
||||
goto empty;
|
||||
if (size_only)
|
||||
return 0;
|
||||
if (S_ISLNK(st.st_mode)) {
|
||||
int ret;
|
||||
s->data = xmalloc(s->size);
|
||||
s->should_free = 1;
|
||||
ret = readlink(s->path, s->data, s->size);
|
||||
if (ret < 0) {
|
||||
free(s->data);
|
||||
goto err_empty;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
fd = open(s->path, O_RDONLY);
|
||||
if (fd < 0)
|
||||
goto err_empty;
|
||||
s->data = mmap(NULL, s->size, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||
close(fd);
|
||||
if (s->data == MAP_FAILED)
|
||||
goto err_empty;
|
||||
s->should_munmap = 1;
|
||||
}
|
||||
else {
|
||||
char type[20];
|
||||
struct sha1_size_cache *e;
|
||||
|
||||
if (size_only) {
|
||||
e = locate_size_cache(s->sha1, 1, 0);
|
||||
if (e) {
|
||||
s->size = e->size;
|
||||
return 0;
|
||||
}
|
||||
if (!sha1_object_info(s->sha1, type, &s->size))
|
||||
locate_size_cache(s->sha1, 0, s->size);
|
||||
}
|
||||
else {
|
||||
s->data = read_sha1_file(s->sha1, type, &s->size);
|
||||
s->should_free = 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void diff_free_filespec_data(struct diff_filespec *s)
|
||||
{
|
||||
if (s->should_free)
|
||||
free(s->data);
|
||||
else if (s->should_munmap)
|
||||
munmap(s->data, s->size);
|
||||
s->should_free = s->should_munmap = 0;
|
||||
s->data = NULL;
|
||||
free(s->cnt_data);
|
||||
s->cnt_data = NULL;
|
||||
}
|
||||
|
||||
static void prep_temp_blob(struct diff_tempfile *temp,
|
||||
void *blob,
|
||||
unsigned long size,
|
||||
const unsigned char *sha1,
|
||||
int mode)
|
||||
{
|
||||
int fd;
|
||||
|
||||
fd = git_mkstemp(temp->tmp_path, TEMPFILE_PATH_LEN, ".diff_XXXXXX");
|
||||
if (fd < 0)
|
||||
die("unable to create temp-file");
|
||||
if (write(fd, blob, size) != size)
|
||||
die("unable to write temp-file");
|
||||
close(fd);
|
||||
temp->name = temp->tmp_path;
|
||||
strcpy(temp->hex, sha1_to_hex(sha1));
|
||||
temp->hex[40] = 0;
|
||||
sprintf(temp->mode, "%06o", mode);
|
||||
}
|
||||
|
||||
static void prepare_temp_file(const char *name,
|
||||
struct diff_tempfile *temp,
|
||||
struct diff_filespec *one)
|
||||
{
|
||||
if (!DIFF_FILE_VALID(one)) {
|
||||
not_a_valid_file:
|
||||
/* A '-' entry produces this for file-2, and
|
||||
* a '+' entry produces this for file-1.
|
||||
*/
|
||||
temp->name = "/dev/null";
|
||||
strcpy(temp->hex, ".");
|
||||
strcpy(temp->mode, ".");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!one->sha1_valid ||
|
||||
work_tree_matches(name, one->sha1)) {
|
||||
struct stat st;
|
||||
if (lstat(name, &st) < 0) {
|
||||
if (errno == ENOENT)
|
||||
goto not_a_valid_file;
|
||||
die("stat(%s): %s", name, strerror(errno));
|
||||
}
|
||||
if (S_ISLNK(st.st_mode)) {
|
||||
int ret;
|
||||
char buf[PATH_MAX + 1]; /* ought to be SYMLINK_MAX */
|
||||
if (sizeof(buf) <= st.st_size)
|
||||
die("symlink too long: %s", name);
|
||||
ret = readlink(name, buf, st.st_size);
|
||||
if (ret < 0)
|
||||
die("readlink(%s)", name);
|
||||
prep_temp_blob(temp, buf, st.st_size,
|
||||
(one->sha1_valid ?
|
||||
one->sha1 : null_sha1),
|
||||
(one->sha1_valid ?
|
||||
one->mode : S_IFLNK));
|
||||
}
|
||||
else {
|
||||
/* we can borrow from the file in the work tree */
|
||||
temp->name = name;
|
||||
if (!one->sha1_valid)
|
||||
strcpy(temp->hex, sha1_to_hex(null_sha1));
|
||||
else
|
||||
strcpy(temp->hex, sha1_to_hex(one->sha1));
|
||||
/* Even though we may sometimes borrow the
|
||||
* contents from the work tree, we always want
|
||||
* one->mode. mode is trustworthy even when
|
||||
* !(one->sha1_valid), as long as
|
||||
* DIFF_FILE_VALID(one).
|
||||
*/
|
||||
sprintf(temp->mode, "%06o", one->mode);
|
||||
}
|
||||
return;
|
||||
}
|
||||
else {
|
||||
if (diff_populate_filespec(one, 0))
|
||||
die("cannot read data blob for %s", one->path);
|
||||
prep_temp_blob(temp, one->data, one->size,
|
||||
one->sha1, one->mode);
|
||||
}
|
||||
}
|
||||
|
||||
static void remove_tempfile(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 2; i++)
|
||||
if (diff_temp[i].name == diff_temp[i].tmp_path) {
|
||||
unlink(diff_temp[i].name);
|
||||
diff_temp[i].name = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void remove_tempfile_on_signal(int signo)
|
||||
{
|
||||
remove_tempfile();
|
||||
signal(SIGINT, SIG_DFL);
|
||||
raise(signo);
|
||||
}
|
||||
|
||||
static int spawn_prog(const char *pgm, const char **arg)
|
||||
{
|
||||
pid_t pid;
|
||||
int status;
|
||||
|
||||
fflush(NULL);
|
||||
pid = fork();
|
||||
if (pid < 0)
|
||||
die("unable to fork");
|
||||
if (!pid) {
|
||||
execvp(pgm, (char *const*) arg);
|
||||
exit(255);
|
||||
}
|
||||
|
||||
while (waitpid(pid, &status, 0) < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Earlier we did not check the exit status because
|
||||
* diff exits non-zero if files are different, and
|
||||
* we are not interested in knowing that. It was a
|
||||
* mistake which made it harder to quit a diff-*
|
||||
* session that uses the git-apply-patch-script as
|
||||
* the GIT_EXTERNAL_DIFF. A custom GIT_EXTERNAL_DIFF
|
||||
* should also exit non-zero only when it wants to
|
||||
* abort the entire diff-* session.
|
||||
*/
|
||||
if (WIFEXITED(status) && !WEXITSTATUS(status))
|
||||
return 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* An external diff command takes:
|
||||
*
|
||||
* diff-cmd name infile1 infile1-sha1 infile1-mode \
|
||||
* infile2 infile2-sha1 infile2-mode [ rename-to ]
|
||||
*
|
||||
*/
|
||||
static void run_external_diff(const char *pgm,
|
||||
const char *name,
|
||||
const char *other,
|
||||
struct diff_filespec *one,
|
||||
struct diff_filespec *two,
|
||||
const char *xfrm_msg,
|
||||
int complete_rewrite)
|
||||
{
|
||||
const char *spawn_arg[10];
|
||||
struct diff_tempfile *temp = diff_temp;
|
||||
int retval;
|
||||
static int atexit_asked = 0;
|
||||
const char *othername;
|
||||
const char **arg = &spawn_arg[0];
|
||||
|
||||
othername = (other? other : name);
|
||||
if (one && two) {
|
||||
prepare_temp_file(name, &temp[0], one);
|
||||
prepare_temp_file(othername, &temp[1], two);
|
||||
if (! atexit_asked &&
|
||||
(temp[0].name == temp[0].tmp_path ||
|
||||
temp[1].name == temp[1].tmp_path)) {
|
||||
atexit_asked = 1;
|
||||
atexit(remove_tempfile);
|
||||
}
|
||||
signal(SIGINT, remove_tempfile_on_signal);
|
||||
}
|
||||
|
||||
if (one && two) {
|
||||
*arg++ = pgm;
|
||||
*arg++ = name;
|
||||
*arg++ = temp[0].name;
|
||||
*arg++ = temp[0].hex;
|
||||
*arg++ = temp[0].mode;
|
||||
*arg++ = temp[1].name;
|
||||
*arg++ = temp[1].hex;
|
||||
*arg++ = temp[1].mode;
|
||||
if (other) {
|
||||
*arg++ = other;
|
||||
*arg++ = xfrm_msg;
|
||||
}
|
||||
} else {
|
||||
*arg++ = pgm;
|
||||
*arg++ = name;
|
||||
}
|
||||
*arg = NULL;
|
||||
retval = spawn_prog(pgm, spawn_arg);
|
||||
remove_tempfile();
|
||||
if (retval) {
|
||||
fprintf(stderr, "external diff died, stopping at %s.\n", name);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
static void run_diff_cmd(const char *pgm,
|
||||
const char *name,
|
||||
const char *other,
|
||||
struct diff_filespec *one,
|
||||
struct diff_filespec *two,
|
||||
const char *xfrm_msg,
|
||||
int complete_rewrite)
|
||||
{
|
||||
if (pgm) {
|
||||
run_external_diff(pgm, name, other, one, two, xfrm_msg,
|
||||
complete_rewrite);
|
||||
return;
|
||||
}
|
||||
if (one && two)
|
||||
builtin_diff(name, other ? other : name,
|
||||
one, two, xfrm_msg, complete_rewrite);
|
||||
else
|
||||
printf("* Unmerged path %s\n", name);
|
||||
}
|
||||
|
||||
static void diff_fill_sha1_info(struct diff_filespec *one)
|
||||
{
|
||||
if (DIFF_FILE_VALID(one)) {
|
||||
if (!one->sha1_valid) {
|
||||
struct stat st;
|
||||
if (lstat(one->path, &st) < 0)
|
||||
die("stat %s", one->path);
|
||||
if (index_path(one->sha1, one->path, &st, 0))
|
||||
die("cannot hash %s\n", one->path);
|
||||
}
|
||||
}
|
||||
else
|
||||
memset(one->sha1, 0, 20);
|
||||
}
|
||||
|
||||
static void run_diff(struct diff_filepair *p, struct diff_options *o)
|
||||
{
|
||||
const char *pgm = external_diff();
|
||||
char msg[PATH_MAX*2+300], *xfrm_msg;
|
||||
struct diff_filespec *one;
|
||||
struct diff_filespec *two;
|
||||
const char *name;
|
||||
const char *other;
|
||||
char *name_munged, *other_munged;
|
||||
int complete_rewrite = 0;
|
||||
int len;
|
||||
|
||||
if (DIFF_PAIR_UNMERGED(p)) {
|
||||
/* unmerged */
|
||||
run_diff_cmd(pgm, p->one->path, NULL, NULL, NULL, NULL, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
name = p->one->path;
|
||||
other = (strcmp(name, p->two->path) ? p->two->path : NULL);
|
||||
name_munged = quote_one(name);
|
||||
other_munged = quote_one(other);
|
||||
one = p->one; two = p->two;
|
||||
|
||||
diff_fill_sha1_info(one);
|
||||
diff_fill_sha1_info(two);
|
||||
|
||||
len = 0;
|
||||
switch (p->status) {
|
||||
case DIFF_STATUS_COPIED:
|
||||
len += snprintf(msg + len, sizeof(msg) - len,
|
||||
"similarity index %d%%\n"
|
||||
"copy from %s\n"
|
||||
"copy to %s\n",
|
||||
(int)(0.5 + p->score * 100.0/MAX_SCORE),
|
||||
name_munged, other_munged);
|
||||
break;
|
||||
case DIFF_STATUS_RENAMED:
|
||||
len += snprintf(msg + len, sizeof(msg) - len,
|
||||
"similarity index %d%%\n"
|
||||
"rename from %s\n"
|
||||
"rename to %s\n",
|
||||
(int)(0.5 + p->score * 100.0/MAX_SCORE),
|
||||
name_munged, other_munged);
|
||||
break;
|
||||
case DIFF_STATUS_MODIFIED:
|
||||
if (p->score) {
|
||||
len += snprintf(msg + len, sizeof(msg) - len,
|
||||
"dissimilarity index %d%%\n",
|
||||
(int)(0.5 + p->score *
|
||||
100.0/MAX_SCORE));
|
||||
complete_rewrite = 1;
|
||||
break;
|
||||
}
|
||||
/* fallthru */
|
||||
default:
|
||||
/* nothing */
|
||||
;
|
||||
}
|
||||
|
||||
if (memcmp(one->sha1, two->sha1, 20)) {
|
||||
int abbrev = o->full_index ? 40 : DEFAULT_ABBREV;
|
||||
|
||||
len += snprintf(msg + len, sizeof(msg) - len,
|
||||
"index %.*s..%.*s",
|
||||
abbrev, sha1_to_hex(one->sha1),
|
||||
abbrev, sha1_to_hex(two->sha1));
|
||||
if (one->mode == two->mode)
|
||||
len += snprintf(msg + len, sizeof(msg) - len,
|
||||
" %06o", one->mode);
|
||||
len += snprintf(msg + len, sizeof(msg) - len, "\n");
|
||||
}
|
||||
|
||||
if (len)
|
||||
msg[--len] = 0;
|
||||
xfrm_msg = len ? msg : NULL;
|
||||
|
||||
if (!pgm &&
|
||||
DIFF_FILE_VALID(one) && DIFF_FILE_VALID(two) &&
|
||||
(S_IFMT & one->mode) != (S_IFMT & two->mode)) {
|
||||
/* a filepair that changes between file and symlink
|
||||
* needs to be split into deletion and creation.
|
||||
*/
|
||||
struct diff_filespec *null = alloc_filespec(two->path);
|
||||
run_diff_cmd(NULL, name, other, one, null, xfrm_msg, 0);
|
||||
free(null);
|
||||
null = alloc_filespec(one->path);
|
||||
run_diff_cmd(NULL, name, other, null, two, xfrm_msg, 0);
|
||||
free(null);
|
||||
}
|
||||
else
|
||||
run_diff_cmd(pgm, name, other, one, two, xfrm_msg,
|
||||
complete_rewrite);
|
||||
|
||||
free(name_munged);
|
||||
free(other_munged);
|
||||
}
|
||||
|
||||
static void run_diffstat(struct diff_filepair *p, struct diff_options *o,
|
||||
struct diffstat_t *diffstat)
|
||||
{
|
||||
const char *name;
|
||||
const char *other;
|
||||
int complete_rewrite = 0;
|
||||
|
||||
if (DIFF_PAIR_UNMERGED(p)) {
|
||||
/* unmerged */
|
||||
builtin_diffstat(p->one->path, NULL, NULL, NULL, diffstat, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
name = p->one->path;
|
||||
other = (strcmp(name, p->two->path) ? p->two->path : NULL);
|
||||
|
||||
diff_fill_sha1_info(p->one);
|
||||
diff_fill_sha1_info(p->two);
|
||||
|
||||
if (p->status == DIFF_STATUS_MODIFIED && p->score)
|
||||
complete_rewrite = 1;
|
||||
builtin_diffstat(name, other, p->one, p->two, diffstat, complete_rewrite);
|
||||
}
|
||||
|
||||
void diff_setup(struct diff_options *options)
|
||||
{
|
||||
memset(options, 0, sizeof(*options));
|
||||
options->output_format = DIFF_FORMAT_RAW;
|
||||
options->line_termination = '\n';
|
||||
options->break_opt = -1;
|
||||
options->rename_limit = -1;
|
||||
|
||||
options->change = diff_change;
|
||||
options->add_remove = diff_addremove;
|
||||
}
|
||||
|
||||
int diff_setup_done(struct diff_options *options)
|
||||
{
|
||||
if ((options->find_copies_harder &&
|
||||
options->detect_rename != DIFF_DETECT_COPY) ||
|
||||
(0 <= options->rename_limit && !options->detect_rename))
|
||||
return -1;
|
||||
|
||||
/*
|
||||
* These cases always need recursive; we do not drop caller-supplied
|
||||
* recursive bits for other formats here.
|
||||
*/
|
||||
if ((options->output_format == DIFF_FORMAT_PATCH) ||
|
||||
(options->output_format == DIFF_FORMAT_DIFFSTAT))
|
||||
options->recursive = 1;
|
||||
|
||||
if (options->detect_rename && options->rename_limit < 0)
|
||||
options->rename_limit = diff_rename_limit_default;
|
||||
if (options->setup & DIFF_SETUP_USE_CACHE) {
|
||||
if (!active_cache)
|
||||
/* read-cache does not die even when it fails
|
||||
* so it is safe for us to do this here. Also
|
||||
* it does not smudge active_cache or active_nr
|
||||
* when it fails, so we do not have to worry about
|
||||
* cleaning it up ourselves either.
|
||||
*/
|
||||
read_cache();
|
||||
}
|
||||
if (options->setup & DIFF_SETUP_USE_SIZE_CACHE)
|
||||
use_size_cache = 1;
|
||||
if (options->abbrev <= 0 || 40 < options->abbrev)
|
||||
options->abbrev = 40; /* full */
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int diff_opt_parse(struct diff_options *options, const char **av, int ac)
|
||||
{
|
||||
const char *arg = av[0];
|
||||
if (!strcmp(arg, "-p") || !strcmp(arg, "-u"))
|
||||
options->output_format = DIFF_FORMAT_PATCH;
|
||||
else if (!strcmp(arg, "--patch-with-raw")) {
|
||||
options->output_format = DIFF_FORMAT_PATCH;
|
||||
options->with_raw = 1;
|
||||
}
|
||||
else if (!strcmp(arg, "--stat"))
|
||||
options->output_format = DIFF_FORMAT_DIFFSTAT;
|
||||
else if (!strcmp(arg, "--patch-with-stat")) {
|
||||
options->output_format = DIFF_FORMAT_PATCH;
|
||||
options->with_stat = 1;
|
||||
}
|
||||
else if (!strcmp(arg, "-z"))
|
||||
options->line_termination = 0;
|
||||
else if (!strncmp(arg, "-l", 2))
|
||||
options->rename_limit = strtoul(arg+2, NULL, 10);
|
||||
else if (!strcmp(arg, "--full-index"))
|
||||
options->full_index = 1;
|
||||
else if (!strcmp(arg, "--name-only"))
|
||||
options->output_format = DIFF_FORMAT_NAME;
|
||||
else if (!strcmp(arg, "--name-status"))
|
||||
options->output_format = DIFF_FORMAT_NAME_STATUS;
|
||||
else if (!strcmp(arg, "-R"))
|
||||
options->reverse_diff = 1;
|
||||
else if (!strncmp(arg, "-S", 2))
|
||||
options->pickaxe = arg + 2;
|
||||
else if (!strcmp(arg, "-s"))
|
||||
options->output_format = DIFF_FORMAT_NO_OUTPUT;
|
||||
else if (!strncmp(arg, "-O", 2))
|
||||
options->orderfile = arg + 2;
|
||||
else if (!strncmp(arg, "--diff-filter=", 14))
|
||||
options->filter = arg + 14;
|
||||
else if (!strcmp(arg, "--pickaxe-all"))
|
||||
options->pickaxe_opts = DIFF_PICKAXE_ALL;
|
||||
else if (!strcmp(arg, "--pickaxe-regex"))
|
||||
options->pickaxe_opts = DIFF_PICKAXE_REGEX;
|
||||
else if (!strncmp(arg, "-B", 2)) {
|
||||
if ((options->break_opt =
|
||||
diff_scoreopt_parse(arg)) == -1)
|
||||
return -1;
|
||||
}
|
||||
else if (!strncmp(arg, "-M", 2)) {
|
||||
if ((options->rename_score =
|
||||
diff_scoreopt_parse(arg)) == -1)
|
||||
return -1;
|
||||
options->detect_rename = DIFF_DETECT_RENAME;
|
||||
}
|
||||
else if (!strncmp(arg, "-C", 2)) {
|
||||
if ((options->rename_score =
|
||||
diff_scoreopt_parse(arg)) == -1)
|
||||
return -1;
|
||||
options->detect_rename = DIFF_DETECT_COPY;
|
||||
}
|
||||
else if (!strcmp(arg, "--find-copies-harder"))
|
||||
options->find_copies_harder = 1;
|
||||
else if (!strcmp(arg, "--abbrev"))
|
||||
options->abbrev = DEFAULT_ABBREV;
|
||||
else if (!strncmp(arg, "--abbrev=", 9)) {
|
||||
options->abbrev = strtoul(arg + 9, NULL, 10);
|
||||
if (options->abbrev < MINIMUM_ABBREV)
|
||||
options->abbrev = MINIMUM_ABBREV;
|
||||
else if (40 < options->abbrev)
|
||||
options->abbrev = 40;
|
||||
}
|
||||
else
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int parse_num(const char **cp_p)
|
||||
{
|
||||
unsigned long num, scale;
|
||||
int ch, dot;
|
||||
const char *cp = *cp_p;
|
||||
|
||||
num = 0;
|
||||
scale = 1;
|
||||
dot = 0;
|
||||
for(;;) {
|
||||
ch = *cp;
|
||||
if ( !dot && ch == '.' ) {
|
||||
scale = 1;
|
||||
dot = 1;
|
||||
} else if ( ch == '%' ) {
|
||||
scale = dot ? scale*100 : 100;
|
||||
cp++; /* % is always at the end */
|
||||
break;
|
||||
} else if ( ch >= '0' && ch <= '9' ) {
|
||||
if ( scale < 100000 ) {
|
||||
scale *= 10;
|
||||
num = (num*10) + (ch-'0');
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
cp++;
|
||||
}
|
||||
*cp_p = cp;
|
||||
|
||||
/* user says num divided by scale and we say internally that
|
||||
* is MAX_SCORE * num / scale.
|
||||
*/
|
||||
return (num >= scale) ? MAX_SCORE : (MAX_SCORE * num / scale);
|
||||
}
|
||||
|
||||
int diff_scoreopt_parse(const char *opt)
|
||||
{
|
||||
int opt1, opt2, cmd;
|
||||
|
||||
if (*opt++ != '-')
|
||||
return -1;
|
||||
cmd = *opt++;
|
||||
if (cmd != 'M' && cmd != 'C' && cmd != 'B')
|
||||
return -1; /* that is not a -M, -C nor -B option */
|
||||
|
||||
opt1 = parse_num(&opt);
|
||||
if (cmd != 'B')
|
||||
opt2 = 0;
|
||||
else {
|
||||
if (*opt == 0)
|
||||
opt2 = 0;
|
||||
else if (*opt != '/')
|
||||
return -1; /* we expect -B80/99 or -B80 */
|
||||
else {
|
||||
opt++;
|
||||
opt2 = parse_num(&opt);
|
||||
}
|
||||
}
|
||||
if (*opt != 0)
|
||||
return -1;
|
||||
return opt1 | (opt2 << 16);
|
||||
}
|
||||
|
||||
struct diff_queue_struct diff_queued_diff;
|
||||
|
||||
void diff_q(struct diff_queue_struct *queue, struct diff_filepair *dp)
|
||||
{
|
||||
if (queue->alloc <= queue->nr) {
|
||||
queue->alloc = alloc_nr(queue->alloc);
|
||||
queue->queue = xrealloc(queue->queue,
|
||||
sizeof(dp) * queue->alloc);
|
||||
}
|
||||
queue->queue[queue->nr++] = dp;
|
||||
}
|
||||
|
||||
struct diff_filepair *diff_queue(struct diff_queue_struct *queue,
|
||||
struct diff_filespec *one,
|
||||
struct diff_filespec *two)
|
||||
{
|
||||
struct diff_filepair *dp = xmalloc(sizeof(*dp));
|
||||
dp->one = one;
|
||||
dp->two = two;
|
||||
dp->score = 0;
|
||||
dp->status = 0;
|
||||
dp->source_stays = 0;
|
||||
dp->broken_pair = 0;
|
||||
if (queue)
|
||||
diff_q(queue, dp);
|
||||
return dp;
|
||||
}
|
||||
|
||||
void diff_free_filepair(struct diff_filepair *p)
|
||||
{
|
||||
diff_free_filespec_data(p->one);
|
||||
diff_free_filespec_data(p->two);
|
||||
free(p->one);
|
||||
free(p->two);
|
||||
free(p);
|
||||
}
|
||||
|
||||
/* This is different from find_unique_abbrev() in that
|
||||
* it stuffs the result with dots for alignment.
|
||||
*/
|
||||
const char *diff_unique_abbrev(const unsigned char *sha1, int len)
|
||||
{
|
||||
int abblen;
|
||||
const char *abbrev;
|
||||
if (len == 40)
|
||||
return sha1_to_hex(sha1);
|
||||
|
||||
abbrev = find_unique_abbrev(sha1, len);
|
||||
if (!abbrev)
|
||||
return sha1_to_hex(sha1);
|
||||
abblen = strlen(abbrev);
|
||||
if (abblen < 37) {
|
||||
static char hex[41];
|
||||
if (len < abblen && abblen <= len + 2)
|
||||
sprintf(hex, "%s%.*s", abbrev, len+3-abblen, "..");
|
||||
else
|
||||
sprintf(hex, "%s...", abbrev);
|
||||
return hex;
|
||||
}
|
||||
return sha1_to_hex(sha1);
|
||||
}
|
||||
|
||||
static void diff_flush_raw(struct diff_filepair *p,
|
||||
int line_termination,
|
||||
int inter_name_termination,
|
||||
struct diff_options *options,
|
||||
int output_format)
|
||||
{
|
||||
int two_paths;
|
||||
char status[10];
|
||||
int abbrev = options->abbrev;
|
||||
const char *path_one, *path_two;
|
||||
|
||||
path_one = p->one->path;
|
||||
path_two = p->two->path;
|
||||
if (line_termination) {
|
||||
path_one = quote_one(path_one);
|
||||
path_two = quote_one(path_two);
|
||||
}
|
||||
|
||||
if (p->score)
|
||||
sprintf(status, "%c%03d", p->status,
|
||||
(int)(0.5 + p->score * 100.0/MAX_SCORE));
|
||||
else {
|
||||
status[0] = p->status;
|
||||
status[1] = 0;
|
||||
}
|
||||
switch (p->status) {
|
||||
case DIFF_STATUS_COPIED:
|
||||
case DIFF_STATUS_RENAMED:
|
||||
two_paths = 1;
|
||||
break;
|
||||
case DIFF_STATUS_ADDED:
|
||||
case DIFF_STATUS_DELETED:
|
||||
two_paths = 0;
|
||||
break;
|
||||
default:
|
||||
two_paths = 0;
|
||||
break;
|
||||
}
|
||||
if (output_format != DIFF_FORMAT_NAME_STATUS) {
|
||||
printf(":%06o %06o %s ",
|
||||
p->one->mode, p->two->mode,
|
||||
diff_unique_abbrev(p->one->sha1, abbrev));
|
||||
printf("%s ",
|
||||
diff_unique_abbrev(p->two->sha1, abbrev));
|
||||
}
|
||||
printf("%s%c%s", status, inter_name_termination, path_one);
|
||||
if (two_paths)
|
||||
printf("%c%s", inter_name_termination, path_two);
|
||||
putchar(line_termination);
|
||||
if (path_one != p->one->path)
|
||||
free((void*)path_one);
|
||||
if (path_two != p->two->path)
|
||||
free((void*)path_two);
|
||||
}
|
||||
|
||||
static void diff_flush_name(struct diff_filepair *p,
|
||||
int inter_name_termination,
|
||||
int line_termination)
|
||||
{
|
||||
char *path = p->two->path;
|
||||
|
||||
if (line_termination)
|
||||
path = quote_one(p->two->path);
|
||||
else
|
||||
path = p->two->path;
|
||||
printf("%s%c", path, line_termination);
|
||||
if (p->two->path != path)
|
||||
free(path);
|
||||
}
|
||||
|
||||
int diff_unmodified_pair(struct diff_filepair *p)
|
||||
{
|
||||
/* This function is written stricter than necessary to support
|
||||
* the currently implemented transformers, but the idea is to
|
||||
* let transformers to produce diff_filepairs any way they want,
|
||||
* and filter and clean them up here before producing the output.
|
||||
*/
|
||||
struct diff_filespec *one, *two;
|
||||
|
||||
if (DIFF_PAIR_UNMERGED(p))
|
||||
return 0; /* unmerged is interesting */
|
||||
|
||||
one = p->one;
|
||||
two = p->two;
|
||||
|
||||
/* deletion, addition, mode or type change
|
||||
* and rename are all interesting.
|
||||
*/
|
||||
if (DIFF_FILE_VALID(one) != DIFF_FILE_VALID(two) ||
|
||||
DIFF_PAIR_MODE_CHANGED(p) ||
|
||||
strcmp(one->path, two->path))
|
||||
return 0;
|
||||
|
||||
/* both are valid and point at the same path. that is, we are
|
||||
* dealing with a change.
|
||||
*/
|
||||
if (one->sha1_valid && two->sha1_valid &&
|
||||
!memcmp(one->sha1, two->sha1, sizeof(one->sha1)))
|
||||
return 1; /* no change */
|
||||
if (!one->sha1_valid && !two->sha1_valid)
|
||||
return 1; /* both look at the same file on the filesystem. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void diff_flush_patch(struct diff_filepair *p, struct diff_options *o)
|
||||
{
|
||||
if (diff_unmodified_pair(p))
|
||||
return;
|
||||
|
||||
if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) ||
|
||||
(DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode)))
|
||||
return; /* no tree diffs in patch format */
|
||||
|
||||
run_diff(p, o);
|
||||
}
|
||||
|
||||
static void diff_flush_stat(struct diff_filepair *p, struct diff_options *o,
|
||||
struct diffstat_t *diffstat)
|
||||
{
|
||||
if (diff_unmodified_pair(p))
|
||||
return;
|
||||
|
||||
if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) ||
|
||||
(DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode)))
|
||||
return; /* no tree diffs in patch format */
|
||||
|
||||
run_diffstat(p, o, diffstat);
|
||||
}
|
||||
|
||||
int diff_queue_is_empty(void)
|
||||
{
|
||||
struct diff_queue_struct *q = &diff_queued_diff;
|
||||
int i;
|
||||
for (i = 0; i < q->nr; i++)
|
||||
if (!diff_unmodified_pair(q->queue[i]))
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
#if DIFF_DEBUG
|
||||
void diff_debug_filespec(struct diff_filespec *s, int x, const char *one)
|
||||
{
|
||||
fprintf(stderr, "queue[%d] %s (%s) %s %06o %s\n",
|
||||
x, one ? one : "",
|
||||
s->path,
|
||||
DIFF_FILE_VALID(s) ? "valid" : "invalid",
|
||||
s->mode,
|
||||
s->sha1_valid ? sha1_to_hex(s->sha1) : "");
|
||||
fprintf(stderr, "queue[%d] %s size %lu flags %d\n",
|
||||
x, one ? one : "",
|
||||
s->size, s->xfrm_flags);
|
||||
}
|
||||
|
||||
void diff_debug_filepair(const struct diff_filepair *p, int i)
|
||||
{
|
||||
diff_debug_filespec(p->one, i, "one");
|
||||
diff_debug_filespec(p->two, i, "two");
|
||||
fprintf(stderr, "score %d, status %c stays %d broken %d\n",
|
||||
p->score, p->status ? p->status : '?',
|
||||
p->source_stays, p->broken_pair);
|
||||
}
|
||||
|
||||
void diff_debug_queue(const char *msg, struct diff_queue_struct *q)
|
||||
{
|
||||
int i;
|
||||
if (msg)
|
||||
fprintf(stderr, "%s\n", msg);
|
||||
fprintf(stderr, "q->nr = %d\n", q->nr);
|
||||
for (i = 0; i < q->nr; i++) {
|
||||
struct diff_filepair *p = q->queue[i];
|
||||
diff_debug_filepair(p, i);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static void diff_resolve_rename_copy(void)
|
||||
{
|
||||
int i, j;
|
||||
struct diff_filepair *p, *pp;
|
||||
struct diff_queue_struct *q = &diff_queued_diff;
|
||||
|
||||
diff_debug_queue("resolve-rename-copy", q);
|
||||
|
||||
for (i = 0; i < q->nr; i++) {
|
||||
p = q->queue[i];
|
||||
p->status = 0; /* undecided */
|
||||
if (DIFF_PAIR_UNMERGED(p))
|
||||
p->status = DIFF_STATUS_UNMERGED;
|
||||
else if (!DIFF_FILE_VALID(p->one))
|
||||
p->status = DIFF_STATUS_ADDED;
|
||||
else if (!DIFF_FILE_VALID(p->two))
|
||||
p->status = DIFF_STATUS_DELETED;
|
||||
else if (DIFF_PAIR_TYPE_CHANGED(p))
|
||||
p->status = DIFF_STATUS_TYPE_CHANGED;
|
||||
|
||||
/* from this point on, we are dealing with a pair
|
||||
* whose both sides are valid and of the same type, i.e.
|
||||
* either in-place edit or rename/copy edit.
|
||||
*/
|
||||
else if (DIFF_PAIR_RENAME(p)) {
|
||||
if (p->source_stays) {
|
||||
p->status = DIFF_STATUS_COPIED;
|
||||
continue;
|
||||
}
|
||||
/* See if there is some other filepair that
|
||||
* copies from the same source as us. If so
|
||||
* we are a copy. Otherwise we are either a
|
||||
* copy if the path stays, or a rename if it
|
||||
* does not, but we already handled "stays" case.
|
||||
*/
|
||||
for (j = i + 1; j < q->nr; j++) {
|
||||
pp = q->queue[j];
|
||||
if (strcmp(pp->one->path, p->one->path))
|
||||
continue; /* not us */
|
||||
if (!DIFF_PAIR_RENAME(pp))
|
||||
continue; /* not a rename/copy */
|
||||
/* pp is a rename/copy from the same source */
|
||||
p->status = DIFF_STATUS_COPIED;
|
||||
break;
|
||||
}
|
||||
if (!p->status)
|
||||
p->status = DIFF_STATUS_RENAMED;
|
||||
}
|
||||
else if (memcmp(p->one->sha1, p->two->sha1, 20) ||
|
||||
p->one->mode != p->two->mode)
|
||||
p->status = DIFF_STATUS_MODIFIED;
|
||||
else {
|
||||
/* This is a "no-change" entry and should not
|
||||
* happen anymore, but prepare for broken callers.
|
||||
*/
|
||||
error("feeding unmodified %s to diffcore",
|
||||
p->one->path);
|
||||
p->status = DIFF_STATUS_UNKNOWN;
|
||||
}
|
||||
}
|
||||
diff_debug_queue("resolve-rename-copy done", q);
|
||||
}
|
||||
|
||||
static void flush_one_pair(struct diff_filepair *p,
|
||||
int diff_output_format,
|
||||
struct diff_options *options,
|
||||
struct diffstat_t *diffstat)
|
||||
{
|
||||
int inter_name_termination = '\t';
|
||||
int line_termination = options->line_termination;
|
||||
if (!line_termination)
|
||||
inter_name_termination = 0;
|
||||
|
||||
switch (p->status) {
|
||||
case DIFF_STATUS_UNKNOWN:
|
||||
break;
|
||||
case 0:
|
||||
die("internal error in diff-resolve-rename-copy");
|
||||
break;
|
||||
default:
|
||||
switch (diff_output_format) {
|
||||
case DIFF_FORMAT_DIFFSTAT:
|
||||
diff_flush_stat(p, options, diffstat);
|
||||
break;
|
||||
case DIFF_FORMAT_PATCH:
|
||||
diff_flush_patch(p, options);
|
||||
break;
|
||||
case DIFF_FORMAT_RAW:
|
||||
case DIFF_FORMAT_NAME_STATUS:
|
||||
diff_flush_raw(p, line_termination,
|
||||
inter_name_termination,
|
||||
options, diff_output_format);
|
||||
break;
|
||||
case DIFF_FORMAT_NAME:
|
||||
diff_flush_name(p,
|
||||
inter_name_termination,
|
||||
line_termination);
|
||||
break;
|
||||
case DIFF_FORMAT_NO_OUTPUT:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void diff_flush(struct diff_options *options)
|
||||
{
|
||||
struct diff_queue_struct *q = &diff_queued_diff;
|
||||
int i;
|
||||
int diff_output_format = options->output_format;
|
||||
struct diffstat_t *diffstat = NULL;
|
||||
|
||||
if (diff_output_format == DIFF_FORMAT_DIFFSTAT || options->with_stat) {
|
||||
diffstat = xcalloc(sizeof (struct diffstat_t), 1);
|
||||
diffstat->xm.consume = diffstat_consume;
|
||||
}
|
||||
|
||||
if (options->with_raw) {
|
||||
for (i = 0; i < q->nr; i++) {
|
||||
struct diff_filepair *p = q->queue[i];
|
||||
flush_one_pair(p, DIFF_FORMAT_RAW, options, NULL);
|
||||
}
|
||||
putchar(options->line_termination);
|
||||
}
|
||||
if (options->with_stat) {
|
||||
for (i = 0; i < q->nr; i++) {
|
||||
struct diff_filepair *p = q->queue[i];
|
||||
flush_one_pair(p, DIFF_FORMAT_DIFFSTAT, options,
|
||||
diffstat);
|
||||
}
|
||||
show_stats(diffstat);
|
||||
free(diffstat);
|
||||
diffstat = NULL;
|
||||
putchar(options->line_termination);
|
||||
}
|
||||
for (i = 0; i < q->nr; i++) {
|
||||
struct diff_filepair *p = q->queue[i];
|
||||
flush_one_pair(p, diff_output_format, options, diffstat);
|
||||
diff_free_filepair(p);
|
||||
}
|
||||
|
||||
if (diffstat) {
|
||||
show_stats(diffstat);
|
||||
free(diffstat);
|
||||
}
|
||||
|
||||
free(q->queue);
|
||||
q->queue = NULL;
|
||||
q->nr = q->alloc = 0;
|
||||
}
|
||||
|
||||
static void diffcore_apply_filter(const char *filter)
|
||||
{
|
||||
int i;
|
||||
struct diff_queue_struct *q = &diff_queued_diff;
|
||||
struct diff_queue_struct outq;
|
||||
outq.queue = NULL;
|
||||
outq.nr = outq.alloc = 0;
|
||||
|
||||
if (!filter)
|
||||
return;
|
||||
|
||||
if (strchr(filter, DIFF_STATUS_FILTER_AON)) {
|
||||
int found;
|
||||
for (i = found = 0; !found && i < q->nr; i++) {
|
||||
struct diff_filepair *p = q->queue[i];
|
||||
if (((p->status == DIFF_STATUS_MODIFIED) &&
|
||||
((p->score &&
|
||||
strchr(filter, DIFF_STATUS_FILTER_BROKEN)) ||
|
||||
(!p->score &&
|
||||
strchr(filter, DIFF_STATUS_MODIFIED)))) ||
|
||||
((p->status != DIFF_STATUS_MODIFIED) &&
|
||||
strchr(filter, p->status)))
|
||||
found++;
|
||||
}
|
||||
if (found)
|
||||
return;
|
||||
|
||||
/* otherwise we will clear the whole queue
|
||||
* by copying the empty outq at the end of this
|
||||
* function, but first clear the current entries
|
||||
* in the queue.
|
||||
*/
|
||||
for (i = 0; i < q->nr; i++)
|
||||
diff_free_filepair(q->queue[i]);
|
||||
}
|
||||
else {
|
||||
/* Only the matching ones */
|
||||
for (i = 0; i < q->nr; i++) {
|
||||
struct diff_filepair *p = q->queue[i];
|
||||
|
||||
if (((p->status == DIFF_STATUS_MODIFIED) &&
|
||||
((p->score &&
|
||||
strchr(filter, DIFF_STATUS_FILTER_BROKEN)) ||
|
||||
(!p->score &&
|
||||
strchr(filter, DIFF_STATUS_MODIFIED)))) ||
|
||||
((p->status != DIFF_STATUS_MODIFIED) &&
|
||||
strchr(filter, p->status)))
|
||||
diff_q(&outq, p);
|
||||
else
|
||||
diff_free_filepair(p);
|
||||
}
|
||||
}
|
||||
free(q->queue);
|
||||
*q = outq;
|
||||
}
|
||||
|
||||
void diffcore_std(struct diff_options *options)
|
||||
{
|
||||
if (options->break_opt != -1)
|
||||
diffcore_break(options->break_opt);
|
||||
if (options->detect_rename)
|
||||
diffcore_rename(options);
|
||||
if (options->break_opt != -1)
|
||||
diffcore_merge_broken();
|
||||
if (options->pickaxe)
|
||||
diffcore_pickaxe(options->pickaxe, options->pickaxe_opts);
|
||||
if (options->orderfile)
|
||||
diffcore_order(options->orderfile);
|
||||
diff_resolve_rename_copy();
|
||||
diffcore_apply_filter(options->filter);
|
||||
}
|
||||
|
||||
|
||||
void diffcore_std_no_resolve(struct diff_options *options)
|
||||
{
|
||||
if (options->pickaxe)
|
||||
diffcore_pickaxe(options->pickaxe, options->pickaxe_opts);
|
||||
if (options->orderfile)
|
||||
diffcore_order(options->orderfile);
|
||||
diffcore_apply_filter(options->filter);
|
||||
}
|
||||
|
||||
void diff_addremove(struct diff_options *options,
|
||||
int addremove, unsigned mode,
|
||||
const unsigned char *sha1,
|
||||
const char *base, const char *path)
|
||||
{
|
||||
char concatpath[PATH_MAX];
|
||||
struct diff_filespec *one, *two;
|
||||
|
||||
/* This may look odd, but it is a preparation for
|
||||
* feeding "there are unchanged files which should
|
||||
* not produce diffs, but when you are doing copy
|
||||
* detection you would need them, so here they are"
|
||||
* entries to the diff-core. They will be prefixed
|
||||
* with something like '=' or '*' (I haven't decided
|
||||
* which but should not make any difference).
|
||||
* Feeding the same new and old to diff_change()
|
||||
* also has the same effect.
|
||||
* Before the final output happens, they are pruned after
|
||||
* merged into rename/copy pairs as appropriate.
|
||||
*/
|
||||
if (options->reverse_diff)
|
||||
addremove = (addremove == '+' ? '-' :
|
||||
addremove == '-' ? '+' : addremove);
|
||||
|
||||
if (!path) path = "";
|
||||
sprintf(concatpath, "%s%s", base, path);
|
||||
one = alloc_filespec(concatpath);
|
||||
two = alloc_filespec(concatpath);
|
||||
|
||||
if (addremove != '+')
|
||||
fill_filespec(one, sha1, mode);
|
||||
if (addremove != '-')
|
||||
fill_filespec(two, sha1, mode);
|
||||
|
||||
diff_queue(&diff_queued_diff, one, two);
|
||||
}
|
||||
|
||||
void diff_change(struct diff_options *options,
|
||||
unsigned old_mode, unsigned new_mode,
|
||||
const unsigned char *old_sha1,
|
||||
const unsigned char *new_sha1,
|
||||
const char *base, const char *path)
|
||||
{
|
||||
char concatpath[PATH_MAX];
|
||||
struct diff_filespec *one, *two;
|
||||
|
||||
if (options->reverse_diff) {
|
||||
unsigned tmp;
|
||||
const unsigned char *tmp_c;
|
||||
tmp = old_mode; old_mode = new_mode; new_mode = tmp;
|
||||
tmp_c = old_sha1; old_sha1 = new_sha1; new_sha1 = tmp_c;
|
||||
}
|
||||
if (!path) path = "";
|
||||
sprintf(concatpath, "%s%s", base, path);
|
||||
one = alloc_filespec(concatpath);
|
||||
two = alloc_filespec(concatpath);
|
||||
fill_filespec(one, old_sha1, old_mode);
|
||||
fill_filespec(two, new_sha1, new_mode);
|
||||
|
||||
diff_queue(&diff_queued_diff, one, two);
|
||||
}
|
||||
|
||||
void diff_unmerge(struct diff_options *options,
|
||||
const char *path)
|
||||
{
|
||||
struct diff_filespec *one, *two;
|
||||
one = alloc_filespec(path);
|
||||
two = alloc_filespec(path);
|
||||
diff_queue(&diff_queued_diff, one, two);
|
||||
}
|
11
diff.h
11
diff.h
|
@ -28,10 +28,11 @@ struct diff_options {
|
|||
with_raw:1,
|
||||
with_stat:1,
|
||||
tree_in_recursive:1,
|
||||
full_index:1;
|
||||
full_index:1,
|
||||
silent_on_remove:1,
|
||||
find_copies_harder:1;
|
||||
int break_opt;
|
||||
int detect_rename;
|
||||
int find_copies_harder;
|
||||
int line_termination;
|
||||
int output_format;
|
||||
int pickaxe_opts;
|
||||
|
@ -74,6 +75,8 @@ struct combine_diff_path {
|
|||
extern void show_combined_diff(struct combine_diff_path *elem, int num_parent,
|
||||
int dense, struct rev_info *);
|
||||
|
||||
extern void diff_tree_combined(const unsigned char *sha1, const unsigned char parent[][20], int num_parent, int dense, struct rev_info *rev);
|
||||
|
||||
extern void diff_tree_combined_merge(const unsigned char *sha1, int, struct rev_info *);
|
||||
|
||||
extern void diff_addremove(struct diff_options *,
|
||||
|
@ -168,4 +171,8 @@ extern void diff_flush(struct diff_options*);
|
|||
|
||||
extern const char *diff_unique_abbrev(const unsigned char *, int);
|
||||
|
||||
extern int run_diff_files(struct rev_info *revs, int silent_on_removed);
|
||||
|
||||
extern int run_diff_index(struct rev_info *revs, int cached);
|
||||
|
||||
#endif /* DIFF_H */
|
||||
|
|
|
@ -13,7 +13,7 @@ char git_default_email[MAX_GITNAME];
|
|||
char git_default_name[MAX_GITNAME];
|
||||
int trust_executable_bit = 1;
|
||||
int assume_unchanged = 0;
|
||||
int only_use_symrefs = 0;
|
||||
int prefer_symlink_refs = 0;
|
||||
int warn_ambiguous_refs = 1;
|
||||
int repository_format_version = 0;
|
||||
char git_commit_encoding[MAX_ENCODING_LENGTH] = "utf-8";
|
||||
|
|
31
git-am.sh
31
git-am.sh
|
@ -14,6 +14,26 @@ stop_here () {
|
|||
exit 1
|
||||
}
|
||||
|
||||
stop_here_user_resolve () {
|
||||
cmdline=$(basename $0)
|
||||
if test '' != "$interactive"
|
||||
then
|
||||
cmdline="$cmdline -i"
|
||||
fi
|
||||
if test '' != "$threeway"
|
||||
then
|
||||
cmdline="$cmdline -3"
|
||||
fi
|
||||
if test '.dotest' != "$dotest"
|
||||
then
|
||||
cmdline="$cmdline -d=$dotest"
|
||||
fi
|
||||
echo "When you have resolved this problem run \"$cmdline --resolved\"."
|
||||
echo "If you would prefer to skip this patch, instead run \"$cmdline --skip\"."
|
||||
|
||||
stop_here $1
|
||||
}
|
||||
|
||||
go_next () {
|
||||
rm -f "$dotest/$msgnum" "$dotest/msg" "$dotest/msg-clean" \
|
||||
"$dotest/patch" "$dotest/info"
|
||||
|
@ -374,7 +394,14 @@ do
|
|||
if test '' = "$changed"
|
||||
then
|
||||
echo "No changes - did you forget update-index?"
|
||||
stop_here $this
|
||||
stop_here_user_resolve $this
|
||||
fi
|
||||
unmerged=$(git-ls-files -u)
|
||||
if test -n "$unmerged"
|
||||
then
|
||||
echo "You still have unmerged paths in your index"
|
||||
echo "did you forget update-index?"
|
||||
stop_here_user_resolve $this
|
||||
fi
|
||||
apply_status=0
|
||||
;;
|
||||
|
@ -400,7 +427,7 @@ do
|
|||
if test $apply_status != 0
|
||||
then
|
||||
echo Patch failed at $msgnum.
|
||||
stop_here $this
|
||||
stop_here_user_resolve $this
|
||||
fi
|
||||
|
||||
if test -x "$GIT_DIR"/hooks/pre-applypatch
|
||||
|
|
|
@ -10,9 +10,10 @@
|
|||
use strict;
|
||||
use Getopt::Long;
|
||||
use POSIX qw(strftime gmtime);
|
||||
use File::Basename qw(basename dirname);
|
||||
|
||||
sub usage() {
|
||||
print STDERR 'Usage: ${\basename $0} [-s] [-S revs-file] file [ revision ]
|
||||
print STDERR "Usage: ${\basename $0} [-s] [-S revs-file] file [ revision ]
|
||||
-l, --long
|
||||
Show long rev (Defaults off)
|
||||
-t, --time
|
||||
|
@ -23,7 +24,7 @@ ()
|
|||
Use revs from revs-file instead of calling git-rev-list
|
||||
-h, --help
|
||||
This message.
|
||||
';
|
||||
";
|
||||
|
||||
exit(1);
|
||||
}
|
||||
|
@ -35,7 +36,7 @@ ()
|
|||
"help|h" => \$help,
|
||||
"rename|r" => \$rename,
|
||||
"rev-file|S=s" => \$rev_file);
|
||||
if (!$rc or $help) {
|
||||
if (!$rc or $help or !@ARGV) {
|
||||
usage();
|
||||
}
|
||||
|
||||
|
@ -208,6 +209,9 @@ sub find_parent_renames {
|
|||
while (my $change = <$patch>) {
|
||||
chomp $change;
|
||||
my $filename = <$patch>;
|
||||
if (!defined $filename) {
|
||||
next;
|
||||
}
|
||||
chomp $filename;
|
||||
|
||||
if ($change =~ m/^[AMD]$/ ) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#!/bin/sh
|
||||
|
||||
USAGE='[(-d | -D) <branchname>] | [[-f] <branchname> [<start-point>]]'
|
||||
USAGE='[(-d | -D) <branchname>] | [[-f] <branchname> [<start-point>]] | -r'
|
||||
LONG_USAGE='If no arguments, show available branches and mark current branch with a star.
|
||||
If one argument, create a new branch <branchname> based off of current HEAD.
|
||||
If two arguments, create a new branch <branchname> based off of <start-point>.'
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# Copyright (c) 2005 Junio C Hamano
|
||||
#
|
||||
|
||||
GIT_DIR=`git-rev-parse --git-dir` || exit $?
|
||||
|
||||
dc </dev/null 2>/dev/null || {
|
||||
# This is not a real DC at all -- it just knows how
|
||||
# this script feeds DC and does the computation itself.
|
||||
dc () {
|
||||
while read a b
|
||||
do
|
||||
case $a,$b in
|
||||
0,) acc=0 ;;
|
||||
*,+) acc=$(($acc + $a)) ;;
|
||||
p,) echo "$acc" ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
}
|
||||
|
||||
echo $(find "$GIT_DIR/objects"/?? -type f -print 2>/dev/null | wc -l) objects, \
|
||||
$({
|
||||
echo 0
|
||||
# "no-such" is to help Darwin folks by not using xargs -r.
|
||||
find "$GIT_DIR/objects"/?? -type f -print 2>/dev/null |
|
||||
xargs du -k "$GIT_DIR/objects/no-such" 2>/dev/null |
|
||||
sed -e 's/[ ].*/ +/'
|
||||
echo p
|
||||
} | dc) kilobytes
|
|
@ -88,7 +88,7 @@
|
|||
$log->debug("Temporary directory is '$TEMP_DIR'");
|
||||
|
||||
# if we are called with a pserver argument,
|
||||
# deal with the authentication cat before entereing the
|
||||
# deal with the authentication cat before entering the
|
||||
# main loop
|
||||
if (@ARGV && $ARGV[0] eq 'pserver') {
|
||||
my $line = <STDIN>; chomp $line;
|
||||
|
@ -117,7 +117,7 @@
|
|||
{
|
||||
chomp;
|
||||
|
||||
# Check to see if we've seen this method, and call appropiate function.
|
||||
# Check to see if we've seen this method, and call appropriate function.
|
||||
if ( /^([\w-]+)(?:\s+(.*))?$/ and defined($methods->{$1}) )
|
||||
{
|
||||
# use the $methods hash to call the appropriate sub for this command
|
||||
|
@ -171,11 +171,11 @@ sub req_Root
|
|||
return 0;
|
||||
}
|
||||
|
||||
my @gitvars = `git-var -l`;
|
||||
my @gitvars = `git-repo-config -l`;
|
||||
if ($?) {
|
||||
print "E problems executing git-var on the server -- this is not a git repository or the PATH is not set correcly.\n";
|
||||
print "E problems executing git-repo-config on the server -- this is not a git repository or the PATH is not set correctly.\n";
|
||||
print "E \n";
|
||||
print "error 1 - problem executing git-var\n";
|
||||
print "error 1 - problem executing git-repo-config\n";
|
||||
return 0;
|
||||
}
|
||||
foreach my $line ( @gitvars )
|
||||
|
@ -224,7 +224,7 @@ sub req_Globaloption
|
|||
sub req_Validresponses
|
||||
{
|
||||
my ( $cmd, $data ) = @_;
|
||||
$log->debug("req_Validrepsonses : $data");
|
||||
$log->debug("req_Validresponses : $data");
|
||||
|
||||
# TODO : re-enable this, currently it's not particularly useful
|
||||
#$state->{validresponses} = [ split /\s+/, $data ];
|
||||
|
@ -733,7 +733,7 @@ sub req_update
|
|||
argsplit("update");
|
||||
|
||||
#
|
||||
# It may just be a client exploring the available heads/modukles
|
||||
# It may just be a client exploring the available heads/modules
|
||||
# in that case, list them as top level directories and leave it
|
||||
# at that. Eclipse uses this technique to offer you a list of
|
||||
# projects (heads in this case) to checkout.
|
||||
|
@ -1731,7 +1731,7 @@ sub transmitfile
|
|||
}
|
||||
|
||||
# This method takes a file name, and returns ( $dirpart, $filepart ) which
|
||||
# refers to the directory porition and the file portion of the filename
|
||||
# refers to the directory portion and the file portion of the filename
|
||||
# respectively
|
||||
sub filenamesplit
|
||||
{
|
||||
|
@ -1790,7 +1790,7 @@ =head1 METHODS
|
|||
=head2 new
|
||||
|
||||
Creates a new log object, optionally you can specify a filename here to
|
||||
indicate the file to log to. If no log file is specified, you can specifiy one
|
||||
indicate the file to log to. If no log file is specified, you can specify one
|
||||
later with method setfile, or indicate you no longer want logging with method
|
||||
nofile.
|
||||
|
||||
|
@ -2076,14 +2076,15 @@ sub update
|
|||
# TODO: log processing is memory bound
|
||||
# if we can parse into a 2nd file that is in reverse order
|
||||
# we can probably do something really efficient
|
||||
my @git_log_params = ('--parents', '--topo-order');
|
||||
my @git_log_params = ('--pretty', '--parents', '--topo-order');
|
||||
|
||||
if (defined $lastcommit) {
|
||||
push @git_log_params, "$lastcommit..$self->{module}";
|
||||
} else {
|
||||
push @git_log_params, $self->{module};
|
||||
}
|
||||
open(GITLOG, '-|', 'git-log', @git_log_params) or die "Cannot call git-log: $!";
|
||||
# git-rev-list is the backend / plumbing version of git-log
|
||||
open(GITLOG, '-|', 'git-rev-list', @git_log_params) or die "Cannot call git-rev-list: $!";
|
||||
|
||||
my @commits;
|
||||
|
||||
|
@ -2595,7 +2596,7 @@ sub in_array
|
|||
|
||||
=head2 safe_pipe_capture
|
||||
|
||||
an alterative to `command` that allows input to be passed as an array
|
||||
an alternative to `command` that allows input to be passed as an array
|
||||
to work around shell problems with weird characters in arguments
|
||||
|
||||
=cut
|
||||
|
|
74
git-diff.sh
74
git-diff.sh
|
@ -1,74 +0,0 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# Copyright (c) 2005 Linus Torvalds
|
||||
# Copyright (c) 2005 Junio C Hamano
|
||||
|
||||
USAGE='[ --diff-options ] <ent>{0,2} [<path>...]'
|
||||
SUBDIRECTORY_OK='Yes'
|
||||
. git-sh-setup
|
||||
|
||||
rev=$(git-rev-parse --revs-only --no-flags --sq "$@") || exit
|
||||
flags=$(git-rev-parse --no-revs --flags --sq "$@")
|
||||
files=$(git-rev-parse --no-revs --no-flags --sq "$@")
|
||||
|
||||
# I often say 'git diff --cached -p' and get scolded by git-diff-files, but
|
||||
# obviously I mean 'git diff --cached -p HEAD' in that case.
|
||||
case "$rev" in
|
||||
'')
|
||||
case " $flags " in
|
||||
*" '--cached' "*)
|
||||
rev='HEAD '
|
||||
;;
|
||||
esac
|
||||
esac
|
||||
|
||||
# If we have -[123] --ours --theirs --base, don't do --cc by default.
|
||||
case " $flags " in
|
||||
*" '-"[123]"' "* | *" '--ours' "* | *" '--base' "* | *" '--theirs' "*)
|
||||
cc_or_p=-p ;;
|
||||
*)
|
||||
cc_or_p=--cc ;;
|
||||
esac
|
||||
|
||||
# If we do not have --name-status, --name-only, -r, -c or --stat,
|
||||
# default to --cc.
|
||||
case " $flags " in
|
||||
*" '--name-status' "* | *" '--name-only' "* | *" '-r' "* | *" '-c' "* | \
|
||||
*" '--stat' "*)
|
||||
;;
|
||||
*)
|
||||
flags="$flags'$cc_or_p' " ;;
|
||||
esac
|
||||
|
||||
# If we do not have -B, -C, -r, nor -p, default to -M.
|
||||
case " $flags " in
|
||||
*" '-"[BCMrp]* | *" '--find-copies-harder' "*)
|
||||
;; # something like -M50.
|
||||
*)
|
||||
flags="$flags'-M' " ;;
|
||||
esac
|
||||
|
||||
case "$rev" in
|
||||
?*' '?*' '?*)
|
||||
usage
|
||||
;;
|
||||
?*' '^?*)
|
||||
begin=$(expr "$rev" : '.*^.\([0-9a-f]*\).*') &&
|
||||
end=$(expr "$rev" : '.\([0-9a-f]*\). .*') || exit
|
||||
cmd="git-diff-tree $flags $begin $end -- $files"
|
||||
;;
|
||||
?*' '?*)
|
||||
cmd="git-diff-tree $flags $rev -- $files"
|
||||
;;
|
||||
?*' ')
|
||||
cmd="git-diff-index $flags $rev -- $files"
|
||||
;;
|
||||
'')
|
||||
cmd="git-diff-files $flags -- $files"
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
|
||||
eval "$cmd"
|
16
git-fetch.sh
16
git-fetch.sh
|
@ -270,14 +270,22 @@ fetch_main () {
|
|||
if [ -n "$GIT_SSL_NO_VERIFY" ]; then
|
||||
curl_extra_args="-k"
|
||||
fi
|
||||
remote_name_quoted=$(perl -e '
|
||||
max_depth=5
|
||||
depth=0
|
||||
head="ref: $remote_name"
|
||||
while (expr "z$head" : "zref:" && expr $depth \< $max_depth) >/dev/null
|
||||
do
|
||||
remote_name_quoted=$(perl -e '
|
||||
my $u = $ARGV[0];
|
||||
$u =~ s/^ref:\s*//;
|
||||
$u =~ s{([^-a-zA-Z0-9/.])}{sprintf"%%%02x",ord($1)}eg;
|
||||
print "$u";
|
||||
' "$remote_name")
|
||||
head=$(curl -nsfL $curl_extra_args "$remote/$remote_name_quoted") &&
|
||||
' "$head")
|
||||
head=$(curl -nsfL $curl_extra_args "$remote/$remote_name_quoted")
|
||||
depth=$( expr \( $depth + 1 \) )
|
||||
done
|
||||
expr "z$head" : "z$_x40\$" >/dev/null ||
|
||||
die "Failed to fetch $remote_name from $remote"
|
||||
die "Failed to fetch $remote_name from $remote"
|
||||
echo >&2 Fetching "$remote_name from $remote" using http
|
||||
git-http-fetch -v -a "$head" "$remote/" || exit
|
||||
;;
|
||||
|
|
|
@ -205,11 +205,10 @@ sub show_date {
|
|||
}
|
||||
my $t = $time + $minutes * 60;
|
||||
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday) = gmtime($t);
|
||||
return sprintf("%s %s %d %02d:%02d:%02d %d %+05d",
|
||||
$weekday_names[$wday],
|
||||
$month_names[$mon],
|
||||
$mday, $hour, $min, $sec,
|
||||
$year+1900, $tz);
|
||||
return sprintf("%s, %d %s %d %02d:%02d:%02d %+05d",
|
||||
$weekday_names[$wday], $mday,
|
||||
$month_names[$mon], $year+1900,
|
||||
$hour, $min, $sec, $tz);
|
||||
}
|
||||
|
||||
print "From nobody Mon Sep 17 00:00:00 2001\n";
|
||||
|
|
|
@ -4,37 +4,51 @@
|
|||
#
|
||||
|
||||
USAGE='[--onto <newbase>] <upstream> [<branch>]'
|
||||
LONG_USAGE='git-rebase applies to <upstream> (or optionally to <newbase>) commits
|
||||
from <branch> that do not appear in <upstream>. When <branch> is not
|
||||
specified it defaults to the current branch (HEAD).
|
||||
LONG_USAGE='git-rebase replaces <branch> with a new branch of the
|
||||
same name. When the --onto option is provided the new branch starts
|
||||
out with a HEAD equal to <newbase>, otherwise it is equal to <upstream>
|
||||
It then attempts to create a new commit for each commit from the original
|
||||
<branch> that does not exist in the <upstream> branch.
|
||||
|
||||
When git-rebase is complete, <branch> will be updated to point to the
|
||||
newly created line of commit objects, so the previous line will not be
|
||||
accessible unless there are other references to it already.
|
||||
It is possible that a merge failure will prevent this process from being
|
||||
completely automatic. You will have to resolve any such merge failure
|
||||
and run git-rebase --continue. If you can not resolve the merge failure,
|
||||
running git-rebase --abort will restore the original <branch> and remove
|
||||
the working files found in the .dotest directory.
|
||||
|
||||
Assuming the following history:
|
||||
Note that if <branch> is not specified on the command line, the
|
||||
currently checked out branch is used. You must be in the top
|
||||
directory of your project to start (or continue) a rebase.
|
||||
|
||||
A---B---C topic
|
||||
/
|
||||
D---E---F---G master
|
||||
Example: git-rebase master~1 topic
|
||||
|
||||
The result of the following command:
|
||||
|
||||
git-rebase --onto master~1 master topic
|
||||
|
||||
would be:
|
||||
|
||||
A'\''--B'\''--C'\'' topic
|
||||
/
|
||||
D---E---F---G master
|
||||
A---B---C topic A'\''--B'\''--C'\'' topic
|
||||
/ --> /
|
||||
D---E---F---G master D---E---F---G master
|
||||
'
|
||||
|
||||
. git-sh-setup
|
||||
|
||||
unset newbase
|
||||
while case "$#" in 0) break ;; esac
|
||||
do
|
||||
case "$1" in
|
||||
--continue)
|
||||
diff=$(git-diff-files)
|
||||
case "$diff" in
|
||||
?*) echo "You must edit all merge conflicts and then"
|
||||
echo "mark them as resolved using git update-index"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
git am --resolved --3way
|
||||
exit
|
||||
;;
|
||||
--abort)
|
||||
[ -d .dotest ] || die "No rebase in progress?"
|
||||
git reset --hard ORIG_HEAD
|
||||
rm -r .dotest
|
||||
exit
|
||||
;;
|
||||
--onto)
|
||||
test 2 -le "$#" || usage
|
||||
newbase="$2"
|
||||
|
@ -107,7 +121,7 @@ onto=$(git-rev-parse --verify "${onto_name}^0") || exit
|
|||
|
||||
# Check if we are already based on $onto, but this should be
|
||||
# done only when upstream and onto are the same.
|
||||
if test "$upstream" = "onto"
|
||||
if test "$upstream" = "$onto"
|
||||
then
|
||||
mb=$(git-merge-base "$onto" "$branch")
|
||||
if test "$mb" = "$onto"
|
||||
|
|
|
@ -291,6 +291,13 @@ sub send_message
|
|||
my $to = join (",\n\t", @recipients);
|
||||
@recipients = unique_email_list(@recipients,@cc);
|
||||
my $date = strftime('%a, %d %b %Y %H:%M:%S %z', localtime($time++));
|
||||
my $gitversion = '@@GIT_VERSION@@';
|
||||
if ($gitversion =~ m/..GIT_VERSION../) {
|
||||
$gitversion = `git --version`;
|
||||
chomp $gitversion;
|
||||
# keep only what's after the last space
|
||||
$gitversion =~ s/^.* //;
|
||||
}
|
||||
|
||||
my $header = "From: $from
|
||||
To: $to
|
||||
|
@ -299,7 +306,7 @@ sub send_message
|
|||
Reply-To: $from
|
||||
Date: $date
|
||||
Message-Id: $message_id
|
||||
X-Mailer: git-send-email @@GIT_VERSION@@
|
||||
X-Mailer: git-send-email $gitversion
|
||||
";
|
||||
$header .= "In-Reply-To: $reply_to\n" if $reply_to;
|
||||
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
USAGE='[-p] [--max-count=<n>] [<since>..<limit>] [--pretty=<format>] [-m] [git-diff-tree options] [git-rev-list options]'
|
||||
SUBDIRECTORY_OK='Yes'
|
||||
. git-sh-setup
|
||||
|
||||
diff_tree_flags=$(git-rev-parse --sq --no-revs --flags "$@") || exit
|
||||
case "$0" in
|
||||
*whatchanged)
|
||||
count=
|
||||
test -z "$diff_tree_flags" &&
|
||||
diff_tree_flags=$(git-repo-config --get whatchanged.difftree)
|
||||
diff_tree_default_flags='-c -M --abbrev' ;;
|
||||
*show)
|
||||
count=-n1
|
||||
test -z "$diff_tree_flags" &&
|
||||
diff_tree_flags=$(git-repo-config --get show.difftree)
|
||||
diff_tree_default_flags='--cc --always' ;;
|
||||
esac
|
||||
test -z "$diff_tree_flags" &&
|
||||
diff_tree_flags="$diff_tree_default_flags"
|
||||
|
||||
rev_list_args=$(git-rev-parse --sq --default HEAD --revs-only "$@") &&
|
||||
diff_tree_args=$(git-rev-parse --sq --no-revs --no-flags "$@") &&
|
||||
|
||||
eval "git-rev-list $count $rev_list_args" |
|
||||
eval "git-diff-tree --stdin --pretty -r $diff_tree_flags $diff_tree_args" |
|
||||
LESS="$LESS -S" ${PAGER:-less}
|
4
git.c
4
git.c
|
@ -8,7 +8,6 @@
|
|||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <stdarg.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include "git-compat-util.h"
|
||||
#include "exec_cmd.h"
|
||||
|
||||
|
@ -47,7 +46,10 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
|
|||
{ "log", cmd_log },
|
||||
{ "whatchanged", cmd_whatchanged },
|
||||
{ "show", cmd_show },
|
||||
{ "push", cmd_push },
|
||||
{ "fmt-patch", cmd_format_patch },
|
||||
{ "count-objects", cmd_count_objects },
|
||||
{ "diff", cmd_diff },
|
||||
};
|
||||
int i;
|
||||
|
||||
|
|
1793
gitk
1793
gitk
|
@ -16,99 +16,112 @@ proc gitdir {} {
|
|||
}
|
||||
}
|
||||
|
||||
proc parse_args {rargs} {
|
||||
global parsed_args
|
||||
|
||||
if {[catch {
|
||||
set parse_args [concat --default HEAD $rargs]
|
||||
set parsed_args [split [eval exec git-rev-parse $parse_args] "\n"]
|
||||
}]} {
|
||||
# if git-rev-parse failed for some reason...
|
||||
if {$rargs == {}} {
|
||||
set rargs HEAD
|
||||
}
|
||||
set parsed_args $rargs
|
||||
}
|
||||
return $parsed_args
|
||||
}
|
||||
|
||||
proc start_rev_list {rlargs} {
|
||||
proc start_rev_list {view} {
|
||||
global startmsecs nextupdate ncmupdate
|
||||
global commfd leftover tclencoding datemode
|
||||
global viewargs viewfiles commitidx
|
||||
|
||||
set startmsecs [clock clicks -milliseconds]
|
||||
set nextupdate [expr {$startmsecs + 100}]
|
||||
set ncmupdate 1
|
||||
initlayout
|
||||
set commitidx($view) 0
|
||||
set args $viewargs($view)
|
||||
if {$viewfiles($view) ne {}} {
|
||||
set args [concat $args "--" $viewfiles($view)]
|
||||
}
|
||||
set order "--topo-order"
|
||||
if {$datemode} {
|
||||
set order "--date-order"
|
||||
}
|
||||
if {[catch {
|
||||
set commfd [open [concat | git-rev-list --header $order \
|
||||
--parents --boundary $rlargs] r]
|
||||
set fd [open [concat | git-rev-list --header $order \
|
||||
--parents --boundary --default HEAD $args] r]
|
||||
} err]} {
|
||||
puts stderr "Error executing git-rev-list: $err"
|
||||
exit 1
|
||||
}
|
||||
set leftover {}
|
||||
fconfigure $commfd -blocking 0 -translation lf
|
||||
set commfd($view) $fd
|
||||
set leftover($view) {}
|
||||
fconfigure $fd -blocking 0 -translation lf
|
||||
if {$tclencoding != {}} {
|
||||
fconfigure $commfd -encoding $tclencoding
|
||||
fconfigure $fd -encoding $tclencoding
|
||||
}
|
||||
fileevent $commfd readable [list getcommitlines $commfd]
|
||||
. config -cursor watch
|
||||
settextcursor watch
|
||||
fileevent $fd readable [list getcommitlines $fd $view]
|
||||
nowbusy $view
|
||||
}
|
||||
|
||||
proc getcommits {rargs} {
|
||||
global phase canv mainfont
|
||||
proc stop_rev_list {} {
|
||||
global commfd curview
|
||||
|
||||
if {![info exists commfd($curview)]} return
|
||||
set fd $commfd($curview)
|
||||
catch {
|
||||
set pid [pid $fd]
|
||||
exec kill $pid
|
||||
}
|
||||
catch {close $fd}
|
||||
unset commfd($curview)
|
||||
}
|
||||
|
||||
proc getcommits {} {
|
||||
global phase canv mainfont curview
|
||||
|
||||
set phase getcommits
|
||||
start_rev_list [parse_args $rargs]
|
||||
$canv delete all
|
||||
$canv create text 3 3 -anchor nw -text "Reading commits..." \
|
||||
-font $mainfont -tags textitems
|
||||
initlayout
|
||||
start_rev_list $curview
|
||||
show_status "Reading commits..."
|
||||
}
|
||||
|
||||
proc getcommitlines {commfd} {
|
||||
proc getcommitlines {fd view} {
|
||||
global commitlisted nextupdate
|
||||
global leftover
|
||||
global leftover commfd
|
||||
global displayorder commitidx commitrow commitdata
|
||||
global parentlist childlist children
|
||||
global parentlist childlist children curview hlview
|
||||
global vparentlist vchildlist vdisporder vcmitlisted
|
||||
|
||||
set stuff [read $commfd]
|
||||
set stuff [read $fd]
|
||||
if {$stuff == {}} {
|
||||
if {![eof $commfd]} return
|
||||
if {![eof $fd]} return
|
||||
global viewname
|
||||
unset commfd($view)
|
||||
notbusy $view
|
||||
# set it blocking so we wait for the process to terminate
|
||||
fconfigure $commfd -blocking 1
|
||||
if {![catch {close $commfd} err]} {
|
||||
fconfigure $fd -blocking 1
|
||||
if {[catch {close $fd} err]} {
|
||||
set fv {}
|
||||
if {$view != $curview} {
|
||||
set fv " for the \"$viewname($view)\" view"
|
||||
}
|
||||
if {[string range $err 0 4] == "usage"} {
|
||||
set err "Gitk: error reading commits$fv:\
|
||||
bad arguments to git-rev-list."
|
||||
if {$viewname($view) eq "Command line"} {
|
||||
append err \
|
||||
" (Note: arguments to gitk are passed to git-rev-list\
|
||||
to allow selection of commits to be displayed.)"
|
||||
}
|
||||
} else {
|
||||
set err "Error reading commits$fv: $err"
|
||||
}
|
||||
error_popup $err
|
||||
}
|
||||
if {$view == $curview} {
|
||||
after idle finishcommits
|
||||
return
|
||||
}
|
||||
if {[string range $err 0 4] == "usage"} {
|
||||
set err \
|
||||
"Gitk: error reading commits: bad arguments to git-rev-list.\
|
||||
(Note: arguments to gitk are passed to git-rev-list\
|
||||
to allow selection of commits to be displayed.)"
|
||||
} else {
|
||||
set err "Error reading commits: $err"
|
||||
}
|
||||
error_popup $err
|
||||
exit 1
|
||||
return
|
||||
}
|
||||
set start 0
|
||||
set gotsome 0
|
||||
while 1 {
|
||||
set i [string first "\0" $stuff $start]
|
||||
if {$i < 0} {
|
||||
append leftover [string range $stuff $start end]
|
||||
append leftover($view) [string range $stuff $start end]
|
||||
break
|
||||
}
|
||||
if {$start == 0} {
|
||||
set cmit $leftover
|
||||
set cmit $leftover($view)
|
||||
append cmit [string range $stuff 0 [expr {$i - 1}]]
|
||||
set leftover {}
|
||||
set leftover($view) {}
|
||||
} else {
|
||||
set cmit [string range $stuff $start [expr {$i - 1}]]
|
||||
}
|
||||
|
@ -141,41 +154,52 @@ proc getcommitlines {commfd} {
|
|||
set id [lindex $ids 0]
|
||||
if {$listed} {
|
||||
set olds [lrange $ids 1 end]
|
||||
if {[llength $olds] > 1} {
|
||||
set olds [lsort -unique $olds]
|
||||
}
|
||||
set i 0
|
||||
foreach p $olds {
|
||||
lappend children($p) $id
|
||||
if {$i == 0 || [lsearch -exact $olds $p] >= $i} {
|
||||
lappend children($view,$p) $id
|
||||
}
|
||||
incr i
|
||||
}
|
||||
} else {
|
||||
set olds {}
|
||||
}
|
||||
lappend parentlist $olds
|
||||
if {[info exists children($id)]} {
|
||||
lappend childlist $children($id)
|
||||
} else {
|
||||
lappend childlist {}
|
||||
if {![info exists children($view,$id)]} {
|
||||
set children($view,$id) {}
|
||||
}
|
||||
set commitdata($id) [string range $cmit [expr {$j + 1}] end]
|
||||
set commitrow($id) $commitidx
|
||||
incr commitidx
|
||||
lappend displayorder $id
|
||||
lappend commitlisted $listed
|
||||
set commitrow($view,$id) $commitidx($view)
|
||||
incr commitidx($view)
|
||||
if {$view == $curview} {
|
||||
lappend parentlist $olds
|
||||
lappend childlist $children($view,$id)
|
||||
lappend displayorder $id
|
||||
lappend commitlisted $listed
|
||||
} else {
|
||||
lappend vparentlist($view) $olds
|
||||
lappend vchildlist($view) $children($view,$id)
|
||||
lappend vdisporder($view) $id
|
||||
lappend vcmitlisted($view) $listed
|
||||
}
|
||||
set gotsome 1
|
||||
}
|
||||
if {$gotsome} {
|
||||
layoutmore
|
||||
if {$view == $curview} {
|
||||
layoutmore
|
||||
} elseif {[info exists hlview] && $view == $hlview} {
|
||||
highlightmore
|
||||
}
|
||||
}
|
||||
if {[clock clicks -milliseconds] >= $nextupdate} {
|
||||
doupdate 1
|
||||
doupdate
|
||||
}
|
||||
}
|
||||
|
||||
proc doupdate {reading} {
|
||||
proc doupdate {} {
|
||||
global commfd nextupdate numcommits ncmupdate
|
||||
|
||||
if {$reading} {
|
||||
fileevent $commfd readable {}
|
||||
foreach v [array names commfd] {
|
||||
fileevent $commfd($v) readable {}
|
||||
}
|
||||
update
|
||||
set nextupdate [expr {[clock clicks -milliseconds] + 100}]
|
||||
|
@ -186,8 +210,9 @@ proc doupdate {reading} {
|
|||
} else {
|
||||
set ncmupdate [expr {$numcommits + 100}]
|
||||
}
|
||||
if {$reading} {
|
||||
fileevent $commfd readable [list getcommitlines $commfd]
|
||||
foreach v [array names commfd] {
|
||||
set fd $commfd($v)
|
||||
fileevent $fd readable [list getcommitlines $fd $v]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -196,18 +221,23 @@ proc readcommit {id} {
|
|||
parsecommit $id $contents 0
|
||||
}
|
||||
|
||||
proc updatecommits {rargs} {
|
||||
stopfindproc
|
||||
foreach v {colormap selectedline matchinglines treediffs
|
||||
mergefilelist currentid rowtextx commitrow
|
||||
rowidlist rowoffsets idrowranges idrangedrawn iddrawn
|
||||
linesegends crossings cornercrossings} {
|
||||
global $v
|
||||
catch {unset $v}
|
||||
proc updatecommits {} {
|
||||
global viewdata curview phase displayorder
|
||||
global children commitrow
|
||||
|
||||
if {$phase ne {}} {
|
||||
stop_rev_list
|
||||
set phase {}
|
||||
}
|
||||
allcanvs delete all
|
||||
set n $curview
|
||||
foreach id $displayorder {
|
||||
catch {unset children($n,$id)}
|
||||
catch {unset commitrow($n,$id)}
|
||||
}
|
||||
set curview -1
|
||||
catch {unset viewdata($n)}
|
||||
readrefs
|
||||
getcommits $rargs
|
||||
showview $n
|
||||
}
|
||||
|
||||
proc parsecommit {id contents listed} {
|
||||
|
@ -290,10 +320,16 @@ proc readrefs {} {
|
|||
match id path]} {
|
||||
continue
|
||||
}
|
||||
if {[regexp {^remotes/.*/HEAD$} $path match]} {
|
||||
continue
|
||||
}
|
||||
if {![regexp {^(tags|heads)/(.*)$} $path match type name]} {
|
||||
set type others
|
||||
set name $path
|
||||
}
|
||||
if {[regexp {^remotes/} $path match]} {
|
||||
set type heads
|
||||
}
|
||||
if {$type == "tags"} {
|
||||
set tagids($name) $id
|
||||
lappend idtags($id) $name
|
||||
|
@ -321,10 +357,7 @@ proc readrefs {} {
|
|||
close $refd
|
||||
}
|
||||
|
||||
proc error_popup msg {
|
||||
set w .error
|
||||
toplevel $w
|
||||
wm transient $w .
|
||||
proc show_error {w msg} {
|
||||
message $w.m -text $msg -justify center -aspect 400
|
||||
pack $w.m -side top -fill x -padx 20 -pady 20
|
||||
button $w.ok -text OK -command "destroy $w"
|
||||
|
@ -334,8 +367,16 @@ proc error_popup msg {
|
|||
tkwait window $w
|
||||
}
|
||||
|
||||
proc makewindow {rargs} {
|
||||
global canv canv2 canv3 linespc charspc ctext cflist textfont mainfont uifont
|
||||
proc error_popup msg {
|
||||
set w .error
|
||||
toplevel $w
|
||||
wm transient $w .
|
||||
show_error $w $msg
|
||||
}
|
||||
|
||||
proc makewindow {} {
|
||||
global canv canv2 canv3 linespc charspc ctext cflist
|
||||
global textfont mainfont uifont
|
||||
global findtype findtypemenu findloc findstring fstring geometry
|
||||
global entries sha1entry sha1string sha1but
|
||||
global maincursor textcursor curtextcursor
|
||||
|
@ -345,7 +386,7 @@ proc makewindow {rargs} {
|
|||
.bar add cascade -label "File" -menu .bar.file
|
||||
.bar configure -font $uifont
|
||||
menu .bar.file
|
||||
.bar.file add command -label "Update" -command [list updatecommits $rargs]
|
||||
.bar.file add command -label "Update" -command updatecommits
|
||||
.bar.file add command -label "Reread references" -command rereadrefs
|
||||
.bar.file add command -label "Quit" -command doquit
|
||||
.bar.file configure -font $uifont
|
||||
|
@ -353,6 +394,23 @@ proc makewindow {rargs} {
|
|||
.bar add cascade -label "Edit" -menu .bar.edit
|
||||
.bar.edit add command -label "Preferences" -command doprefs
|
||||
.bar.edit configure -font $uifont
|
||||
|
||||
menu .bar.view -font $uifont
|
||||
menu .bar.view.hl -font $uifont -tearoff 0
|
||||
.bar add cascade -label "View" -menu .bar.view
|
||||
.bar.view add command -label "New view..." -command {newview 0}
|
||||
.bar.view add command -label "Edit view..." -command editview \
|
||||
-state disabled
|
||||
.bar.view add command -label "Delete view" -command delview -state disabled
|
||||
.bar.view add cascade -label "Highlight" -menu .bar.view.hl
|
||||
.bar.view add separator
|
||||
.bar.view add radiobutton -label "All files" -command {showview 0} \
|
||||
-variable selectedview -value 0
|
||||
.bar.view.hl add command -label "New view..." -command {newview 1}
|
||||
.bar.view.hl add command -label "Remove" -command delhighlight \
|
||||
-state disabled
|
||||
.bar.view.hl add separator
|
||||
|
||||
menu .bar.help
|
||||
.bar add cascade -label "Help" -menu .bar.help
|
||||
.bar.help add command -label "About gitk" -command about
|
||||
|
@ -463,7 +521,7 @@ proc makewindow {rargs} {
|
|||
set ctext .ctop.cdet.left.ctext
|
||||
text $ctext -bg white -state disabled -font $textfont \
|
||||
-width $geometry(ctextw) -height $geometry(ctexth) \
|
||||
-yscrollcommand ".ctop.cdet.left.sb set" -wrap none
|
||||
-yscrollcommand {.ctop.cdet.left.sb set} -wrap none
|
||||
scrollbar .ctop.cdet.left.sb -command "$ctext yview"
|
||||
pack .ctop.cdet.left.sb -side right -fill y
|
||||
pack $ctext -side left -fill both -expand 1
|
||||
|
@ -496,12 +554,25 @@ proc makewindow {rargs} {
|
|||
$ctext tag conf found -back yellow
|
||||
|
||||
frame .ctop.cdet.right
|
||||
frame .ctop.cdet.right.mode
|
||||
radiobutton .ctop.cdet.right.mode.patch -text "Patch" \
|
||||
-command reselectline -variable cmitmode -value "patch"
|
||||
radiobutton .ctop.cdet.right.mode.tree -text "Tree" \
|
||||
-command reselectline -variable cmitmode -value "tree"
|
||||
grid .ctop.cdet.right.mode.patch .ctop.cdet.right.mode.tree -sticky ew
|
||||
pack .ctop.cdet.right.mode -side top -fill x
|
||||
set cflist .ctop.cdet.right.cfiles
|
||||
listbox $cflist -bg white -selectmode extended -width $geometry(cflistw) \
|
||||
-yscrollcommand ".ctop.cdet.right.sb set" -font $mainfont
|
||||
set indent [font measure $mainfont "nn"]
|
||||
text $cflist -width $geometry(cflistw) -background white -font $mainfont \
|
||||
-tabs [list $indent [expr {2 * $indent}]] \
|
||||
-yscrollcommand ".ctop.cdet.right.sb set" \
|
||||
-cursor [. cget -cursor] \
|
||||
-spacing1 1 -spacing3 1
|
||||
scrollbar .ctop.cdet.right.sb -command "$cflist yview"
|
||||
pack .ctop.cdet.right.sb -side right -fill y
|
||||
pack $cflist -side left -fill both -expand 1
|
||||
$cflist tag configure highlight \
|
||||
-background [$cflist cget -selectbackground]
|
||||
.ctop.cdet add .ctop.cdet.right
|
||||
bind .ctop.cdet <Configure> {resizecdetpanes %W %w}
|
||||
|
||||
|
@ -553,12 +624,14 @@ proc makewindow {rargs} {
|
|||
bind . <Control-KP_Add> {incrfont 1}
|
||||
bind . <Control-minus> {incrfont -1}
|
||||
bind . <Control-KP_Subtract> {incrfont -1}
|
||||
bind $cflist <<ListboxSelect>> listboxsel
|
||||
bind . <Destroy> {savestuff %W}
|
||||
bind . <Button-1> "click %W"
|
||||
bind $fstring <Key-Return> dofind
|
||||
bind $sha1entry <Key-Return> gotocommit
|
||||
bind $sha1entry <<PasteSelection>> clearsha1
|
||||
bind $cflist <1> {sel_flist %W %x %y; break}
|
||||
bind $cflist <B1-Motion> {sel_flist %W %x %y; break}
|
||||
bind $cflist <ButtonRelease-1> {treeclick %W %x %y}
|
||||
|
||||
set maincursor [. cget -cursor]
|
||||
set textcursor [$ctext cget -cursor]
|
||||
|
@ -622,6 +695,8 @@ proc savestuff {w} {
|
|||
global canv canv2 canv3 ctext cflist mainfont textfont uifont
|
||||
global stuffsaved findmergefiles maxgraphpct
|
||||
global maxwidth
|
||||
global viewname viewfiles viewargs viewperm nextviewnum
|
||||
global cmitmode
|
||||
|
||||
if {$stuffsaved} return
|
||||
if {![winfo viewable .]} return
|
||||
|
@ -633,6 +708,7 @@ proc savestuff {w} {
|
|||
puts $f [list set findmergefiles $findmergefiles]
|
||||
puts $f [list set maxgraphpct $maxgraphpct]
|
||||
puts $f [list set maxwidth $maxwidth]
|
||||
puts $f [list set cmitmode $cmitmode]
|
||||
puts $f "set geometry(width) [winfo width .ctop]"
|
||||
puts $f "set geometry(height) [winfo height .ctop]"
|
||||
puts $f "set geometry(canv1) [expr {[winfo width $canv]-2}]"
|
||||
|
@ -645,6 +721,13 @@ proc savestuff {w} {
|
|||
set wid [expr {([winfo width $cflist] - 11) \
|
||||
/ [font measure [$cflist cget -font] "0"]}]
|
||||
puts $f "set geometry(cflistw) $wid"
|
||||
puts -nonewline $f "set permviews {"
|
||||
for {set v 0} {$v < $nextviewnum} {incr v} {
|
||||
if {$viewperm($v)} {
|
||||
puts $f "{[list $viewname($v) $viewfiles($v) $viewargs($v)]}"
|
||||
}
|
||||
}
|
||||
puts $f "}"
|
||||
close $f
|
||||
file rename -force "~/.gitk-new" "~/.gitk"
|
||||
}
|
||||
|
@ -786,6 +869,754 @@ f Scroll diff view to next file
|
|||
pack $w.ok -side bottom
|
||||
}
|
||||
|
||||
# Procedures for manipulating the file list window at the
|
||||
# bottom right of the overall window.
|
||||
|
||||
proc treeview {w l openlevs} {
|
||||
global treecontents treediropen treeheight treeparent treeindex
|
||||
|
||||
set ix 0
|
||||
set treeindex() 0
|
||||
set lev 0
|
||||
set prefix {}
|
||||
set prefixend -1
|
||||
set prefendstack {}
|
||||
set htstack {}
|
||||
set ht 0
|
||||
set treecontents() {}
|
||||
$w conf -state normal
|
||||
foreach f $l {
|
||||
while {[string range $f 0 $prefixend] ne $prefix} {
|
||||
if {$lev <= $openlevs} {
|
||||
$w mark set e:$treeindex($prefix) "end -1c"
|
||||
$w mark gravity e:$treeindex($prefix) left
|
||||
}
|
||||
set treeheight($prefix) $ht
|
||||
incr ht [lindex $htstack end]
|
||||
set htstack [lreplace $htstack end end]
|
||||
set prefixend [lindex $prefendstack end]
|
||||
set prefendstack [lreplace $prefendstack end end]
|
||||
set prefix [string range $prefix 0 $prefixend]
|
||||
incr lev -1
|
||||
}
|
||||
set tail [string range $f [expr {$prefixend+1}] end]
|
||||
while {[set slash [string first "/" $tail]] >= 0} {
|
||||
lappend htstack $ht
|
||||
set ht 0
|
||||
lappend prefendstack $prefixend
|
||||
incr prefixend [expr {$slash + 1}]
|
||||
set d [string range $tail 0 $slash]
|
||||
lappend treecontents($prefix) $d
|
||||
set oldprefix $prefix
|
||||
append prefix $d
|
||||
set treecontents($prefix) {}
|
||||
set treeindex($prefix) [incr ix]
|
||||
set treeparent($prefix) $oldprefix
|
||||
set tail [string range $tail [expr {$slash+1}] end]
|
||||
if {$lev <= $openlevs} {
|
||||
set ht 1
|
||||
set treediropen($prefix) [expr {$lev < $openlevs}]
|
||||
set bm [expr {$lev == $openlevs? "tri-rt": "tri-dn"}]
|
||||
$w mark set d:$ix "end -1c"
|
||||
$w mark gravity d:$ix left
|
||||
set str "\n"
|
||||
for {set i 0} {$i < $lev} {incr i} {append str "\t"}
|
||||
$w insert end $str
|
||||
$w image create end -align center -image $bm -padx 1 \
|
||||
-name a:$ix
|
||||
$w insert end $d
|
||||
$w mark set s:$ix "end -1c"
|
||||
$w mark gravity s:$ix left
|
||||
}
|
||||
incr lev
|
||||
}
|
||||
if {$tail ne {}} {
|
||||
if {$lev <= $openlevs} {
|
||||
incr ht
|
||||
set str "\n"
|
||||
for {set i 0} {$i < $lev} {incr i} {append str "\t"}
|
||||
$w insert end $str
|
||||
$w insert end $tail
|
||||
}
|
||||
lappend treecontents($prefix) $tail
|
||||
}
|
||||
}
|
||||
while {$htstack ne {}} {
|
||||
set treeheight($prefix) $ht
|
||||
incr ht [lindex $htstack end]
|
||||
set htstack [lreplace $htstack end end]
|
||||
}
|
||||
$w conf -state disabled
|
||||
}
|
||||
|
||||
proc linetoelt {l} {
|
||||
global treeheight treecontents
|
||||
|
||||
set y 2
|
||||
set prefix {}
|
||||
while {1} {
|
||||
foreach e $treecontents($prefix) {
|
||||
if {$y == $l} {
|
||||
return "$prefix$e"
|
||||
}
|
||||
set n 1
|
||||
if {[string index $e end] eq "/"} {
|
||||
set n $treeheight($prefix$e)
|
||||
if {$y + $n > $l} {
|
||||
append prefix $e
|
||||
incr y
|
||||
break
|
||||
}
|
||||
}
|
||||
incr y $n
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
proc treeclosedir {w dir} {
|
||||
global treediropen treeheight treeparent treeindex
|
||||
|
||||
set ix $treeindex($dir)
|
||||
$w conf -state normal
|
||||
$w delete s:$ix e:$ix
|
||||
set treediropen($dir) 0
|
||||
$w image configure a:$ix -image tri-rt
|
||||
$w conf -state disabled
|
||||
set n [expr {1 - $treeheight($dir)}]
|
||||
while {$dir ne {}} {
|
||||
incr treeheight($dir) $n
|
||||
set dir $treeparent($dir)
|
||||
}
|
||||
}
|
||||
|
||||
proc treeopendir {w dir} {
|
||||
global treediropen treeheight treeparent treecontents treeindex
|
||||
|
||||
set ix $treeindex($dir)
|
||||
$w conf -state normal
|
||||
$w image configure a:$ix -image tri-dn
|
||||
$w mark set e:$ix s:$ix
|
||||
$w mark gravity e:$ix right
|
||||
set lev 0
|
||||
set str "\n"
|
||||
set n [llength $treecontents($dir)]
|
||||
for {set x $dir} {$x ne {}} {set x $treeparent($x)} {
|
||||
incr lev
|
||||
append str "\t"
|
||||
incr treeheight($x) $n
|
||||
}
|
||||
foreach e $treecontents($dir) {
|
||||
if {[string index $e end] eq "/"} {
|
||||
set de $dir$e
|
||||
set iy $treeindex($de)
|
||||
$w mark set d:$iy e:$ix
|
||||
$w mark gravity d:$iy left
|
||||
$w insert e:$ix $str
|
||||
set treediropen($de) 0
|
||||
$w image create e:$ix -align center -image tri-rt -padx 1 \
|
||||
-name a:$iy
|
||||
$w insert e:$ix $e
|
||||
$w mark set s:$iy e:$ix
|
||||
$w mark gravity s:$iy left
|
||||
set treeheight($de) 1
|
||||
} else {
|
||||
$w insert e:$ix $str
|
||||
$w insert e:$ix $e
|
||||
}
|
||||
}
|
||||
$w mark gravity e:$ix left
|
||||
$w conf -state disabled
|
||||
set treediropen($dir) 1
|
||||
set top [lindex [split [$w index @0,0] .] 0]
|
||||
set ht [$w cget -height]
|
||||
set l [lindex [split [$w index s:$ix] .] 0]
|
||||
if {$l < $top} {
|
||||
$w yview $l.0
|
||||
} elseif {$l + $n + 1 > $top + $ht} {
|
||||
set top [expr {$l + $n + 2 - $ht}]
|
||||
if {$l < $top} {
|
||||
set top $l
|
||||
}
|
||||
$w yview $top.0
|
||||
}
|
||||
}
|
||||
|
||||
proc treeclick {w x y} {
|
||||
global treediropen cmitmode ctext cflist cflist_top
|
||||
|
||||
if {$cmitmode ne "tree"} return
|
||||
if {![info exists cflist_top]} return
|
||||
set l [lindex [split [$w index "@$x,$y"] "."] 0]
|
||||
$cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
|
||||
$cflist tag add highlight $l.0 "$l.0 lineend"
|
||||
set cflist_top $l
|
||||
if {$l == 1} {
|
||||
$ctext yview 1.0
|
||||
return
|
||||
}
|
||||
set e [linetoelt $l]
|
||||
if {[string index $e end] ne "/"} {
|
||||
showfile $e
|
||||
} elseif {$treediropen($e)} {
|
||||
treeclosedir $w $e
|
||||
} else {
|
||||
treeopendir $w $e
|
||||
}
|
||||
}
|
||||
|
||||
proc setfilelist {id} {
|
||||
global treefilelist cflist
|
||||
|
||||
treeview $cflist $treefilelist($id) 0
|
||||
}
|
||||
|
||||
image create bitmap tri-rt -background black -foreground blue -data {
|
||||
#define tri-rt_width 13
|
||||
#define tri-rt_height 13
|
||||
static unsigned char tri-rt_bits[] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x70, 0x00, 0xf0, 0x00,
|
||||
0xf0, 0x01, 0xf0, 0x00, 0x70, 0x00, 0x30, 0x00, 0x10, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00};
|
||||
} -maskdata {
|
||||
#define tri-rt-mask_width 13
|
||||
#define tri-rt-mask_height 13
|
||||
static unsigned char tri-rt-mask_bits[] = {
|
||||
0x08, 0x00, 0x18, 0x00, 0x38, 0x00, 0x78, 0x00, 0xf8, 0x00, 0xf8, 0x01,
|
||||
0xf8, 0x03, 0xf8, 0x01, 0xf8, 0x00, 0x78, 0x00, 0x38, 0x00, 0x18, 0x00,
|
||||
0x08, 0x00};
|
||||
}
|
||||
image create bitmap tri-dn -background black -foreground blue -data {
|
||||
#define tri-dn_width 13
|
||||
#define tri-dn_height 13
|
||||
static unsigned char tri-dn_bits[] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x07, 0xf8, 0x03,
|
||||
0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00};
|
||||
} -maskdata {
|
||||
#define tri-dn-mask_width 13
|
||||
#define tri-dn-mask_height 13
|
||||
static unsigned char tri-dn-mask_bits[] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x1f, 0xfe, 0x0f, 0xfc, 0x07,
|
||||
0xf8, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00};
|
||||
}
|
||||
|
||||
proc init_flist {first} {
|
||||
global cflist cflist_top selectedline difffilestart
|
||||
|
||||
$cflist conf -state normal
|
||||
$cflist delete 0.0 end
|
||||
if {$first ne {}} {
|
||||
$cflist insert end $first
|
||||
set cflist_top 1
|
||||
$cflist tag add highlight 1.0 "1.0 lineend"
|
||||
} else {
|
||||
catch {unset cflist_top}
|
||||
}
|
||||
$cflist conf -state disabled
|
||||
set difffilestart {}
|
||||
}
|
||||
|
||||
proc add_flist {fl} {
|
||||
global flistmode cflist
|
||||
|
||||
$cflist conf -state normal
|
||||
if {$flistmode eq "flat"} {
|
||||
foreach f $fl {
|
||||
$cflist insert end "\n$f"
|
||||
}
|
||||
}
|
||||
$cflist conf -state disabled
|
||||
}
|
||||
|
||||
proc sel_flist {w x y} {
|
||||
global flistmode ctext difffilestart cflist cflist_top cmitmode
|
||||
|
||||
if {$cmitmode eq "tree"} return
|
||||
if {![info exists cflist_top]} return
|
||||
set l [lindex [split [$w index "@$x,$y"] "."] 0]
|
||||
$cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
|
||||
$cflist tag add highlight $l.0 "$l.0 lineend"
|
||||
set cflist_top $l
|
||||
if {$l == 1} {
|
||||
$ctext yview 1.0
|
||||
} else {
|
||||
catch {$ctext yview [lindex $difffilestart [expr {$l - 2}]]}
|
||||
}
|
||||
}
|
||||
|
||||
# Functions for adding and removing shell-type quoting
|
||||
|
||||
proc shellquote {str} {
|
||||
if {![string match "*\['\"\\ \t]*" $str]} {
|
||||
return $str
|
||||
}
|
||||
if {![string match "*\['\"\\]*" $str]} {
|
||||
return "\"$str\""
|
||||
}
|
||||
if {![string match "*'*" $str]} {
|
||||
return "'$str'"
|
||||
}
|
||||
return "\"[string map {\" \\\" \\ \\\\} $str]\""
|
||||
}
|
||||
|
||||
proc shellarglist {l} {
|
||||
set str {}
|
||||
foreach a $l {
|
||||
if {$str ne {}} {
|
||||
append str " "
|
||||
}
|
||||
append str [shellquote $a]
|
||||
}
|
||||
return $str
|
||||
}
|
||||
|
||||
proc shelldequote {str} {
|
||||
set ret {}
|
||||
set used -1
|
||||
while {1} {
|
||||
incr used
|
||||
if {![regexp -start $used -indices "\['\"\\\\ \t]" $str first]} {
|
||||
append ret [string range $str $used end]
|
||||
set used [string length $str]
|
||||
break
|
||||
}
|
||||
set first [lindex $first 0]
|
||||
set ch [string index $str $first]
|
||||
if {$first > $used} {
|
||||
append ret [string range $str $used [expr {$first - 1}]]
|
||||
set used $first
|
||||
}
|
||||
if {$ch eq " " || $ch eq "\t"} break
|
||||
incr used
|
||||
if {$ch eq "'"} {
|
||||
set first [string first "'" $str $used]
|
||||
if {$first < 0} {
|
||||
error "unmatched single-quote"
|
||||
}
|
||||
append ret [string range $str $used [expr {$first - 1}]]
|
||||
set used $first
|
||||
continue
|
||||
}
|
||||
if {$ch eq "\\"} {
|
||||
if {$used >= [string length $str]} {
|
||||
error "trailing backslash"
|
||||
}
|
||||
append ret [string index $str $used]
|
||||
continue
|
||||
}
|
||||
# here ch == "\""
|
||||
while {1} {
|
||||
if {![regexp -start $used -indices "\[\"\\\\]" $str first]} {
|
||||
error "unmatched double-quote"
|
||||
}
|
||||
set first [lindex $first 0]
|
||||
set ch [string index $str $first]
|
||||
if {$first > $used} {
|
||||
append ret [string range $str $used [expr {$first - 1}]]
|
||||
set used $first
|
||||
}
|
||||
if {$ch eq "\""} break
|
||||
incr used
|
||||
append ret [string index $str $used]
|
||||
incr used
|
||||
}
|
||||
}
|
||||
return [list $used $ret]
|
||||
}
|
||||
|
||||
proc shellsplit {str} {
|
||||
set l {}
|
||||
while {1} {
|
||||
set str [string trimleft $str]
|
||||
if {$str eq {}} break
|
||||
set dq [shelldequote $str]
|
||||
set n [lindex $dq 0]
|
||||
set word [lindex $dq 1]
|
||||
set str [string range $str $n end]
|
||||
lappend l $word
|
||||
}
|
||||
return $l
|
||||
}
|
||||
|
||||
# Code to implement multiple views
|
||||
|
||||
proc newview {ishighlight} {
|
||||
global nextviewnum newviewname newviewperm uifont newishighlight
|
||||
global newviewargs revtreeargs
|
||||
|
||||
set newishighlight $ishighlight
|
||||
set top .gitkview
|
||||
if {[winfo exists $top]} {
|
||||
raise $top
|
||||
return
|
||||
}
|
||||
set newviewname($nextviewnum) "View $nextviewnum"
|
||||
set newviewperm($nextviewnum) 0
|
||||
set newviewargs($nextviewnum) [shellarglist $revtreeargs]
|
||||
vieweditor $top $nextviewnum "Gitk view definition"
|
||||
}
|
||||
|
||||
proc editview {} {
|
||||
global curview
|
||||
global viewname viewperm newviewname newviewperm
|
||||
global viewargs newviewargs
|
||||
|
||||
set top .gitkvedit-$curview
|
||||
if {[winfo exists $top]} {
|
||||
raise $top
|
||||
return
|
||||
}
|
||||
set newviewname($curview) $viewname($curview)
|
||||
set newviewperm($curview) $viewperm($curview)
|
||||
set newviewargs($curview) [shellarglist $viewargs($curview)]
|
||||
vieweditor $top $curview "Gitk: edit view $viewname($curview)"
|
||||
}
|
||||
|
||||
proc vieweditor {top n title} {
|
||||
global newviewname newviewperm viewfiles
|
||||
global uifont
|
||||
|
||||
toplevel $top
|
||||
wm title $top $title
|
||||
label $top.nl -text "Name" -font $uifont
|
||||
entry $top.name -width 20 -textvariable newviewname($n)
|
||||
grid $top.nl $top.name -sticky w -pady 5
|
||||
checkbutton $top.perm -text "Remember this view" -variable newviewperm($n)
|
||||
grid $top.perm - -pady 5 -sticky w
|
||||
message $top.al -aspect 1000 -font $uifont \
|
||||
-text "Commits to include (arguments to git-rev-list):"
|
||||
grid $top.al - -sticky w -pady 5
|
||||
entry $top.args -width 50 -textvariable newviewargs($n) \
|
||||
-background white
|
||||
grid $top.args - -sticky ew -padx 5
|
||||
message $top.l -aspect 1000 -font $uifont \
|
||||
-text "Enter files and directories to include, one per line:"
|
||||
grid $top.l - -sticky w
|
||||
text $top.t -width 40 -height 10 -background white
|
||||
if {[info exists viewfiles($n)]} {
|
||||
foreach f $viewfiles($n) {
|
||||
$top.t insert end $f
|
||||
$top.t insert end "\n"
|
||||
}
|
||||
$top.t delete {end - 1c} end
|
||||
$top.t mark set insert 0.0
|
||||
}
|
||||
grid $top.t - -sticky ew -padx 5
|
||||
frame $top.buts
|
||||
button $top.buts.ok -text "OK" -command [list newviewok $top $n]
|
||||
button $top.buts.can -text "Cancel" -command [list destroy $top]
|
||||
grid $top.buts.ok $top.buts.can
|
||||
grid columnconfigure $top.buts 0 -weight 1 -uniform a
|
||||
grid columnconfigure $top.buts 1 -weight 1 -uniform a
|
||||
grid $top.buts - -pady 10 -sticky ew
|
||||
focus $top.t
|
||||
}
|
||||
|
||||
proc doviewmenu {m first cmd op args} {
|
||||
set nmenu [$m index end]
|
||||
for {set i $first} {$i <= $nmenu} {incr i} {
|
||||
if {[$m entrycget $i -command] eq $cmd} {
|
||||
eval $m $op $i $args
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
proc allviewmenus {n op args} {
|
||||
doviewmenu .bar.view 7 [list showview $n] $op $args
|
||||
doviewmenu .bar.view.hl 3 [list addhighlight $n] $op $args
|
||||
}
|
||||
|
||||
proc newviewok {top n} {
|
||||
global nextviewnum newviewperm newviewname newishighlight
|
||||
global viewname viewfiles viewperm selectedview curview
|
||||
global viewargs newviewargs
|
||||
|
||||
if {[catch {
|
||||
set newargs [shellsplit $newviewargs($n)]
|
||||
} err]} {
|
||||
error_popup "Error in commit selection arguments: $err"
|
||||
wm raise $top
|
||||
focus $top
|
||||
return
|
||||
}
|
||||
set files {}
|
||||
foreach f [split [$top.t get 0.0 end] "\n"] {
|
||||
set ft [string trim $f]
|
||||
if {$ft ne {}} {
|
||||
lappend files $ft
|
||||
}
|
||||
}
|
||||
if {![info exists viewfiles($n)]} {
|
||||
# creating a new view
|
||||
incr nextviewnum
|
||||
set viewname($n) $newviewname($n)
|
||||
set viewperm($n) $newviewperm($n)
|
||||
set viewfiles($n) $files
|
||||
set viewargs($n) $newargs
|
||||
addviewmenu $n
|
||||
if {!$newishighlight} {
|
||||
after idle showview $n
|
||||
} else {
|
||||
after idle addhighlight $n
|
||||
}
|
||||
} else {
|
||||
# editing an existing view
|
||||
set viewperm($n) $newviewperm($n)
|
||||
if {$newviewname($n) ne $viewname($n)} {
|
||||
set viewname($n) $newviewname($n)
|
||||
allviewmenus $n entryconf -label $viewname($n)
|
||||
}
|
||||
if {$files ne $viewfiles($n) || $newargs ne $viewargs($n)} {
|
||||
set viewfiles($n) $files
|
||||
set viewargs($n) $newargs
|
||||
if {$curview == $n} {
|
||||
after idle updatecommits
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {destroy $top}
|
||||
}
|
||||
|
||||
proc delview {} {
|
||||
global curview viewdata viewperm
|
||||
|
||||
if {$curview == 0} return
|
||||
allviewmenus $curview delete
|
||||
set viewdata($curview) {}
|
||||
set viewperm($curview) 0
|
||||
showview 0
|
||||
}
|
||||
|
||||
proc addviewmenu {n} {
|
||||
global viewname
|
||||
|
||||
.bar.view add radiobutton -label $viewname($n) \
|
||||
-command [list showview $n] -variable selectedview -value $n
|
||||
.bar.view.hl add radiobutton -label $viewname($n) \
|
||||
-command [list addhighlight $n] -variable selectedhlview -value $n
|
||||
}
|
||||
|
||||
proc flatten {var} {
|
||||
global $var
|
||||
|
||||
set ret {}
|
||||
foreach i [array names $var] {
|
||||
lappend ret $i [set $var\($i\)]
|
||||
}
|
||||
return $ret
|
||||
}
|
||||
|
||||
proc unflatten {var l} {
|
||||
global $var
|
||||
|
||||
catch {unset $var}
|
||||
foreach {i v} $l {
|
||||
set $var\($i\) $v
|
||||
}
|
||||
}
|
||||
|
||||
proc showview {n} {
|
||||
global curview viewdata viewfiles
|
||||
global displayorder parentlist childlist rowidlist rowoffsets
|
||||
global colormap rowtextx commitrow nextcolor canvxmax
|
||||
global numcommits rowrangelist commitlisted idrowranges
|
||||
global selectedline currentid canv canvy0
|
||||
global matchinglines treediffs
|
||||
global pending_select phase
|
||||
global commitidx rowlaidout rowoptim linesegends
|
||||
global commfd nextupdate
|
||||
global selectedview hlview selectedhlview
|
||||
global vparentlist vchildlist vdisporder vcmitlisted
|
||||
|
||||
if {$n == $curview} return
|
||||
set selid {}
|
||||
if {[info exists selectedline]} {
|
||||
set selid $currentid
|
||||
set y [yc $selectedline]
|
||||
set ymax [lindex [$canv cget -scrollregion] 3]
|
||||
set span [$canv yview]
|
||||
set ytop [expr {[lindex $span 0] * $ymax}]
|
||||
set ybot [expr {[lindex $span 1] * $ymax}]
|
||||
if {$ytop < $y && $y < $ybot} {
|
||||
set yscreen [expr {$y - $ytop}]
|
||||
} else {
|
||||
set yscreen [expr {($ybot - $ytop) / 2}]
|
||||
}
|
||||
}
|
||||
unselectline
|
||||
normalline
|
||||
stopfindproc
|
||||
if {$curview >= 0} {
|
||||
set vparentlist($curview) $parentlist
|
||||
set vchildlist($curview) $childlist
|
||||
set vdisporder($curview) $displayorder
|
||||
set vcmitlisted($curview) $commitlisted
|
||||
if {$phase ne {}} {
|
||||
set viewdata($curview) \
|
||||
[list $phase $rowidlist $rowoffsets $rowrangelist \
|
||||
[flatten idrowranges] [flatten idinlist] \
|
||||
$rowlaidout $rowoptim $numcommits $linesegends]
|
||||
} elseif {![info exists viewdata($curview)]
|
||||
|| [lindex $viewdata($curview) 0] ne {}} {
|
||||
set viewdata($curview) \
|
||||
[list {} $rowidlist $rowoffsets $rowrangelist]
|
||||
}
|
||||
}
|
||||
catch {unset matchinglines}
|
||||
catch {unset treediffs}
|
||||
clear_display
|
||||
|
||||
set curview $n
|
||||
set selectedview $n
|
||||
set selectedhlview -1
|
||||
.bar.view entryconf 2 -state [expr {$n == 0? "disabled": "normal"}]
|
||||
.bar.view entryconf 3 -state [expr {$n == 0? "disabled": "normal"}]
|
||||
catch {unset hlview}
|
||||
.bar.view.hl entryconf 1 -state disabled
|
||||
|
||||
if {![info exists viewdata($n)]} {
|
||||
set pending_select $selid
|
||||
getcommits
|
||||
return
|
||||
}
|
||||
|
||||
set v $viewdata($n)
|
||||
set phase [lindex $v 0]
|
||||
set displayorder $vdisporder($n)
|
||||
set parentlist $vparentlist($n)
|
||||
set childlist $vchildlist($n)
|
||||
set commitlisted $vcmitlisted($n)
|
||||
set rowidlist [lindex $v 1]
|
||||
set rowoffsets [lindex $v 2]
|
||||
set rowrangelist [lindex $v 3]
|
||||
if {$phase eq {}} {
|
||||
set numcommits [llength $displayorder]
|
||||
catch {unset idrowranges}
|
||||
} else {
|
||||
unflatten idrowranges [lindex $v 4]
|
||||
unflatten idinlist [lindex $v 5]
|
||||
set rowlaidout [lindex $v 6]
|
||||
set rowoptim [lindex $v 7]
|
||||
set numcommits [lindex $v 8]
|
||||
set linesegends [lindex $v 9]
|
||||
}
|
||||
|
||||
catch {unset colormap}
|
||||
catch {unset rowtextx}
|
||||
set nextcolor 0
|
||||
set canvxmax [$canv cget -width]
|
||||
set curview $n
|
||||
set row 0
|
||||
setcanvscroll
|
||||
set yf 0
|
||||
set row 0
|
||||
if {$selid ne {} && [info exists commitrow($n,$selid)]} {
|
||||
set row $commitrow($n,$selid)
|
||||
# try to get the selected row in the same position on the screen
|
||||
set ymax [lindex [$canv cget -scrollregion] 3]
|
||||
set ytop [expr {[yc $row] - $yscreen}]
|
||||
if {$ytop < 0} {
|
||||
set ytop 0
|
||||
}
|
||||
set yf [expr {$ytop * 1.0 / $ymax}]
|
||||
}
|
||||
allcanvs yview moveto $yf
|
||||
drawvisible
|
||||
selectline $row 0
|
||||
if {$phase ne {}} {
|
||||
if {$phase eq "getcommits"} {
|
||||
show_status "Reading commits..."
|
||||
}
|
||||
if {[info exists commfd($n)]} {
|
||||
layoutmore
|
||||
} else {
|
||||
finishcommits
|
||||
}
|
||||
} elseif {$numcommits == 0} {
|
||||
show_status "No commits selected"
|
||||
}
|
||||
}
|
||||
|
||||
proc addhighlight {n} {
|
||||
global hlview curview viewdata highlighted highlightedrows
|
||||
global selectedhlview
|
||||
|
||||
if {[info exists hlview]} {
|
||||
delhighlight
|
||||
}
|
||||
set hlview $n
|
||||
set selectedhlview $n
|
||||
.bar.view.hl entryconf 1 -state normal
|
||||
set highlighted($n) 0
|
||||
set highlightedrows {}
|
||||
if {$n != $curview && ![info exists viewdata($n)]} {
|
||||
set viewdata($n) [list getcommits {{}} {{}} {} {} {} 0 0 0 {}]
|
||||
set vparentlist($n) {}
|
||||
set vchildlist($n) {}
|
||||
set vdisporder($n) {}
|
||||
set vcmitlisted($n) {}
|
||||
start_rev_list $n
|
||||
} else {
|
||||
highlightmore
|
||||
}
|
||||
}
|
||||
|
||||
proc delhighlight {} {
|
||||
global hlview highlightedrows canv linehtag mainfont
|
||||
global selectedhlview selectedline
|
||||
|
||||
if {![info exists hlview]} return
|
||||
unset hlview
|
||||
set selectedhlview {}
|
||||
.bar.view.hl entryconf 1 -state disabled
|
||||
foreach l $highlightedrows {
|
||||
$canv itemconf $linehtag($l) -font $mainfont
|
||||
if {$l == $selectedline} {
|
||||
$canv delete secsel
|
||||
set t [eval $canv create rect [$canv bbox $linehtag($l)] \
|
||||
-outline {{}} -tags secsel \
|
||||
-fill [$canv cget -selectbackground]]
|
||||
$canv lower $t
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
proc highlightmore {} {
|
||||
global hlview highlighted commitidx highlightedrows linehtag mainfont
|
||||
global displayorder vdisporder curview canv commitrow selectedline
|
||||
|
||||
set font [concat $mainfont bold]
|
||||
set max $commitidx($hlview)
|
||||
if {$hlview == $curview} {
|
||||
set disp $displayorder
|
||||
} else {
|
||||
set disp $vdisporder($hlview)
|
||||
}
|
||||
for {set i $highlighted($hlview)} {$i < $max} {incr i} {
|
||||
set id [lindex $disp $i]
|
||||
if {[info exists commitrow($curview,$id)]} {
|
||||
set row $commitrow($curview,$id)
|
||||
if {[info exists linehtag($row)]} {
|
||||
$canv itemconf $linehtag($row) -font $font
|
||||
lappend highlightedrows $row
|
||||
if {$row == $selectedline} {
|
||||
$canv delete secsel
|
||||
set t [eval $canv create rect \
|
||||
[$canv bbox $linehtag($row)] \
|
||||
-outline {{}} -tags secsel \
|
||||
-fill [$canv cget -selectbackground]]
|
||||
$canv lower $t
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
set highlighted($hlview) $max
|
||||
}
|
||||
|
||||
# Graph layout functions
|
||||
|
||||
proc shortids {ids} {
|
||||
set res {}
|
||||
foreach id $ids {
|
||||
|
@ -821,20 +1652,21 @@ proc ntimes {n o} {
|
|||
}
|
||||
|
||||
proc usedinrange {id l1 l2} {
|
||||
global children commitrow
|
||||
global children commitrow childlist curview
|
||||
|
||||
if {[info exists commitrow($id)]} {
|
||||
set r $commitrow($id)
|
||||
if {[info exists commitrow($curview,$id)]} {
|
||||
set r $commitrow($curview,$id)
|
||||
if {$l1 <= $r && $r <= $l2} {
|
||||
return [expr {$r - $l1 + 1}]
|
||||
}
|
||||
set kids [lindex $childlist $r]
|
||||
} else {
|
||||
set kids $children($curview,$id)
|
||||
}
|
||||
foreach c $children($id) {
|
||||
if {[info exists commitrow($c)]} {
|
||||
set r $commitrow($c)
|
||||
if {$l1 <= $r && $r <= $l2} {
|
||||
return [expr {$r - $l1 + 1}]
|
||||
}
|
||||
foreach c $kids {
|
||||
set r $commitrow($curview,$c)
|
||||
if {$l1 <= $r && $r <= $l2} {
|
||||
return [expr {$r - $l1 + 1}]
|
||||
}
|
||||
}
|
||||
return 0
|
||||
|
@ -902,18 +1734,19 @@ proc makeuparrow {oid x y z} {
|
|||
proc initlayout {} {
|
||||
global rowidlist rowoffsets displayorder commitlisted
|
||||
global rowlaidout rowoptim
|
||||
global idinlist rowchk
|
||||
global commitidx numcommits canvxmax canv
|
||||
global idinlist rowchk rowrangelist idrowranges
|
||||
global numcommits canvxmax canv
|
||||
global nextcolor
|
||||
global parentlist childlist children
|
||||
global colormap rowtextx
|
||||
global linesegends
|
||||
|
||||
set commitidx 0
|
||||
set numcommits 0
|
||||
set displayorder {}
|
||||
set commitlisted {}
|
||||
set parentlist {}
|
||||
set childlist {}
|
||||
catch {unset children}
|
||||
set rowrangelist {}
|
||||
set nextcolor 0
|
||||
set rowidlist {{}}
|
||||
set rowoffsets {{}}
|
||||
|
@ -922,6 +1755,10 @@ proc initlayout {} {
|
|||
set rowlaidout 0
|
||||
set rowoptim 0
|
||||
set canvxmax [$canv cget -width]
|
||||
catch {unset colormap}
|
||||
catch {unset rowtextx}
|
||||
catch {unset idrowranges}
|
||||
set linesegends {}
|
||||
}
|
||||
|
||||
proc setcanvscroll {} {
|
||||
|
@ -954,13 +1791,12 @@ proc visiblerows {} {
|
|||
|
||||
proc layoutmore {} {
|
||||
global rowlaidout rowoptim commitidx numcommits optim_delay
|
||||
global uparrowlen
|
||||
global uparrowlen curview
|
||||
|
||||
set row $rowlaidout
|
||||
set rowlaidout [layoutrows $row $commitidx 0]
|
||||
set rowlaidout [layoutrows $row $commitidx($curview) 0]
|
||||
set orow [expr {$rowlaidout - $uparrowlen - 1}]
|
||||
if {$orow > $rowoptim} {
|
||||
checkcrossings $rowoptim $orow
|
||||
optimize_rows $rowoptim 0 $orow
|
||||
set rowoptim $orow
|
||||
}
|
||||
|
@ -971,8 +1807,8 @@ proc layoutmore {} {
|
|||
}
|
||||
|
||||
proc showstuff {canshow} {
|
||||
global numcommits
|
||||
global linesegends idrowranges idrangedrawn
|
||||
global numcommits commitrow pending_select selectedline
|
||||
global linesegends idrowranges idrangedrawn curview
|
||||
|
||||
if {$numcommits == 0} {
|
||||
global phase
|
||||
|
@ -985,17 +1821,16 @@ proc showstuff {canshow} {
|
|||
set rows [visiblerows]
|
||||
set r0 [lindex $rows 0]
|
||||
set r1 [lindex $rows 1]
|
||||
set selrow -1
|
||||
for {set r $row} {$r < $canshow} {incr r} {
|
||||
if {[info exists linesegends($r)]} {
|
||||
foreach id $linesegends($r) {
|
||||
set i -1
|
||||
foreach {s e} $idrowranges($id) {
|
||||
incr i
|
||||
if {$e ne {} && $e < $numcommits && $s <= $r1 && $e >= $r0
|
||||
&& ![info exists idrangedrawn($id,$i)]} {
|
||||
drawlineseg $id $i
|
||||
set idrangedrawn($id,$i) 1
|
||||
}
|
||||
foreach id [lindex $linesegends [expr {$r+1}]] {
|
||||
set i -1
|
||||
foreach {s e} [rowranges $id] {
|
||||
incr i
|
||||
if {$e ne {} && $e < $numcommits && $s <= $r1 && $e >= $r0
|
||||
&& ![info exists idrangedrawn($id,$i)]} {
|
||||
drawlineseg $id $i
|
||||
set idrangedrawn($id,$i) 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1007,6 +1842,14 @@ proc showstuff {canshow} {
|
|||
drawcmitrow $row
|
||||
incr row
|
||||
}
|
||||
if {[info exists pending_select] &&
|
||||
[info exists commitrow($curview,$pending_select)] &&
|
||||
$commitrow($curview,$pending_select) < $numcommits} {
|
||||
selectline $commitrow($curview,$pending_select) 1
|
||||
}
|
||||
if {![info exists selectedline] && ![info exists pending_select]} {
|
||||
selectline 0 1
|
||||
}
|
||||
}
|
||||
|
||||
proc layoutrows {row endrow last} {
|
||||
|
@ -1014,8 +1857,8 @@ proc layoutrows {row endrow last} {
|
|||
global uparrowlen downarrowlen maxwidth mingaplen
|
||||
global childlist parentlist
|
||||
global idrowranges linesegends
|
||||
global commitidx
|
||||
global idinlist rowchk
|
||||
global commitidx curview
|
||||
global idinlist rowchk rowrangelist
|
||||
|
||||
set idlist [lindex $rowidlist $row]
|
||||
set offs [lindex $rowoffsets $row]
|
||||
|
@ -1030,10 +1873,12 @@ proc layoutrows {row endrow last} {
|
|||
lappend oldolds $p
|
||||
}
|
||||
}
|
||||
set lse {}
|
||||
set nev [expr {[llength $idlist] + [llength $newolds]
|
||||
+ [llength $oldolds] - $maxwidth + 1}]
|
||||
if {$nev > 0} {
|
||||
if {!$last && $row + $uparrowlen + $mingaplen >= $commitidx} break
|
||||
if {!$last &&
|
||||
$row + $uparrowlen + $mingaplen >= $commitidx($curview)} break
|
||||
for {set x [llength $idlist]} {[incr x -1] >= 0} {} {
|
||||
set i [lindex $idlist $x]
|
||||
if {![info exists rowchk($i)] || $row >= $rowchk($i)} {
|
||||
|
@ -1045,7 +1890,7 @@ proc layoutrows {row endrow last} {
|
|||
set offs [incrange $offs $x 1]
|
||||
set idinlist($i) 0
|
||||
set rm1 [expr {$row - 1}]
|
||||
lappend linesegends($rm1) $i
|
||||
lappend lse $i
|
||||
lappend idrowranges($i) $rm1
|
||||
if {[incr nev -1] <= 0} break
|
||||
continue
|
||||
|
@ -1056,6 +1901,7 @@ proc layoutrows {row endrow last} {
|
|||
lset rowidlist $row $idlist
|
||||
lset rowoffsets $row $offs
|
||||
}
|
||||
lappend linesegends $lse
|
||||
set col [lsearch -exact $idlist $id]
|
||||
if {$col < 0} {
|
||||
set col [llength $idlist]
|
||||
|
@ -1074,9 +1920,13 @@ proc layoutrows {row endrow last} {
|
|||
} else {
|
||||
unset idinlist($id)
|
||||
}
|
||||
set ranges {}
|
||||
if {[info exists idrowranges($id)]} {
|
||||
lappend idrowranges($id) $row
|
||||
set ranges $idrowranges($id)
|
||||
lappend ranges $row
|
||||
unset idrowranges($id)
|
||||
}
|
||||
lappend rowrangelist $ranges
|
||||
incr row
|
||||
set offs [ntimes [llength $idlist] 0]
|
||||
set l [llength $newolds]
|
||||
|
@ -1117,29 +1967,28 @@ proc layoutrows {row endrow last} {
|
|||
proc addextraid {id row} {
|
||||
global displayorder commitrow commitinfo
|
||||
global commitidx commitlisted
|
||||
global parentlist childlist children
|
||||
global parentlist childlist children curview
|
||||
|
||||
incr commitidx
|
||||
incr commitidx($curview)
|
||||
lappend displayorder $id
|
||||
lappend commitlisted 0
|
||||
lappend parentlist {}
|
||||
set commitrow($id) $row
|
||||
set commitrow($curview,$id) $row
|
||||
readcommit $id
|
||||
if {![info exists commitinfo($id)]} {
|
||||
set commitinfo($id) {"No commit information available"}
|
||||
}
|
||||
if {[info exists children($id)]} {
|
||||
lappend childlist $children($id)
|
||||
} else {
|
||||
lappend childlist {}
|
||||
if {![info exists children($curview,$id)]} {
|
||||
set children($curview,$id) {}
|
||||
}
|
||||
lappend childlist $children($curview,$id)
|
||||
}
|
||||
|
||||
proc layouttail {} {
|
||||
global rowidlist rowoffsets idinlist commitidx
|
||||
global idrowranges
|
||||
global rowidlist rowoffsets idinlist commitidx curview
|
||||
global idrowranges rowrangelist
|
||||
|
||||
set row $commitidx
|
||||
set row $commitidx($curview)
|
||||
set idlist [lindex $rowidlist $row]
|
||||
while {$idlist ne {}} {
|
||||
set col [expr {[llength $idlist] - 1}]
|
||||
|
@ -1147,6 +1996,8 @@ proc layouttail {} {
|
|||
addextraid $id $row
|
||||
unset idinlist($id)
|
||||
lappend idrowranges($id) $row
|
||||
lappend rowrangelist $idrowranges($id)
|
||||
unset idrowranges($id)
|
||||
incr row
|
||||
set offs [ntimes $col 0]
|
||||
set idlist [lreplace $idlist $col $col]
|
||||
|
@ -1160,6 +2011,8 @@ proc layouttail {} {
|
|||
lset rowoffsets $row 0
|
||||
makeuparrow $id 0 $row 0
|
||||
lappend idrowranges($id) $row
|
||||
lappend rowrangelist $idrowranges($id)
|
||||
unset idrowranges($id)
|
||||
incr row
|
||||
lappend rowidlist {}
|
||||
lappend rowoffsets {}
|
||||
|
@ -1176,7 +2029,7 @@ proc insert_pad {row col npad} {
|
|||
}
|
||||
|
||||
proc optimize_rows {row col endrow} {
|
||||
global rowidlist rowoffsets idrowranges linesegends displayorder
|
||||
global rowidlist rowoffsets idrowranges displayorder
|
||||
|
||||
for {} {$row < $endrow} {incr row} {
|
||||
set idlist [lindex $rowidlist $row]
|
||||
|
@ -1195,8 +2048,8 @@ proc optimize_rows {row col endrow} {
|
|||
set z0 [lindex $rowoffsets $y0 $x0]
|
||||
if {$z0 eq {}} {
|
||||
set id [lindex $idlist $col]
|
||||
if {[info exists idrowranges($id)] &&
|
||||
$y0 > [lindex $idrowranges($id) 0]} {
|
||||
set ranges [rowranges $id]
|
||||
if {$ranges ne {} && $y0 > [lindex $ranges 0]} {
|
||||
set isarrow 1
|
||||
}
|
||||
}
|
||||
|
@ -1254,8 +2107,8 @@ proc optimize_rows {row col endrow} {
|
|||
if {$o eq {}} {
|
||||
# check if this is the link to the first child
|
||||
set id [lindex $idlist $col]
|
||||
if {[info exists idrowranges($id)] &&
|
||||
$row == [lindex $idrowranges($id) 0]} {
|
||||
set ranges [rowranges $id]
|
||||
if {$ranges ne {} && $row == [lindex $ranges 0]} {
|
||||
# it is, work out offset to child
|
||||
set y0 [expr {$row - 1}]
|
||||
set id [lindex $displayorder $y0]
|
||||
|
@ -1309,13 +2162,36 @@ proc linewidth {id} {
|
|||
return $wid
|
||||
}
|
||||
|
||||
proc rowranges {id} {
|
||||
global phase idrowranges commitrow rowlaidout rowrangelist curview
|
||||
|
||||
set ranges {}
|
||||
if {$phase eq {} ||
|
||||
([info exists commitrow($curview,$id)]
|
||||
&& $commitrow($curview,$id) < $rowlaidout)} {
|
||||
set ranges [lindex $rowrangelist $commitrow($curview,$id)]
|
||||
} elseif {[info exists idrowranges($id)]} {
|
||||
set ranges $idrowranges($id)
|
||||
}
|
||||
return $ranges
|
||||
}
|
||||
|
||||
proc drawlineseg {id i} {
|
||||
global rowoffsets rowidlist idrowranges
|
||||
global rowoffsets rowidlist
|
||||
global displayorder
|
||||
global canv colormap linespc
|
||||
global numcommits commitrow curview
|
||||
|
||||
set startrow [lindex $idrowranges($id) [expr {2 * $i}]]
|
||||
set row [lindex $idrowranges($id) [expr {2 * $i + 1}]]
|
||||
set ranges [rowranges $id]
|
||||
set downarrow 1
|
||||
if {[info exists commitrow($curview,$id)]
|
||||
&& $commitrow($curview,$id) < $numcommits} {
|
||||
set downarrow [expr {$i < [llength $ranges] / 2 - 1}]
|
||||
} else {
|
||||
set downarrow 1
|
||||
}
|
||||
set startrow [lindex $ranges [expr {2 * $i}]]
|
||||
set row [lindex $ranges [expr {2 * $i + 1}]]
|
||||
if {$startrow == $row} return
|
||||
assigncolor $id
|
||||
set coords {}
|
||||
|
@ -1359,8 +2235,7 @@ proc drawlineseg {id i} {
|
|||
}
|
||||
}
|
||||
if {[llength $coords] < 4} return
|
||||
set last [expr {[llength $idrowranges($id)] / 2 - 1}]
|
||||
if {$i < $last} {
|
||||
if {$downarrow} {
|
||||
# This line has an arrow at the lower end: check if the arrow is
|
||||
# on a diagonal segment, and if so, work around the Tk 8.4
|
||||
# refusal to draw arrows on diagonal lines.
|
||||
|
@ -1380,7 +2255,7 @@ proc drawlineseg {id i} {
|
|||
}
|
||||
}
|
||||
}
|
||||
set arrow [expr {2 * ($i > 0) + ($i < $last)}]
|
||||
set arrow [expr {2 * ($i > 0) + $downarrow}]
|
||||
set arrow [lindex {none first last both} $arrow]
|
||||
set t [$canv create line $coords -width [linewidth $id] \
|
||||
-fill $colormap($id) -tags lines.$id -arrow $arrow]
|
||||
|
@ -1389,7 +2264,7 @@ proc drawlineseg {id i} {
|
|||
}
|
||||
|
||||
proc drawparentlinks {id row col olds} {
|
||||
global rowidlist canv colormap idrowranges
|
||||
global rowidlist canv colormap
|
||||
|
||||
set row2 [expr {$row + 1}]
|
||||
set x [xc $row $col]
|
||||
|
@ -1408,9 +2283,9 @@ proc drawparentlinks {id row col olds} {
|
|||
if {$x2 > $rmx} {
|
||||
set rmx $x2
|
||||
}
|
||||
if {[info exists idrowranges($p)] &&
|
||||
$row2 == [lindex $idrowranges($p) 0] &&
|
||||
$row2 < [lindex $idrowranges($p) 1]} {
|
||||
set ranges [rowranges $p]
|
||||
if {$ranges ne {} && $row2 == [lindex $ranges 0]
|
||||
&& $row2 < [lindex $ranges 1]} {
|
||||
# drawlineseg will do this one for us
|
||||
continue
|
||||
}
|
||||
|
@ -1433,19 +2308,19 @@ proc drawparentlinks {id row col olds} {
|
|||
|
||||
proc drawlines {id} {
|
||||
global colormap canv
|
||||
global idrowranges idrangedrawn
|
||||
global childlist iddrawn commitrow rowidlist
|
||||
global idrangedrawn
|
||||
global children iddrawn commitrow rowidlist curview
|
||||
|
||||
$canv delete lines.$id
|
||||
set nr [expr {[llength $idrowranges($id)] / 2}]
|
||||
set nr [expr {[llength [rowranges $id]] / 2}]
|
||||
for {set i 0} {$i < $nr} {incr i} {
|
||||
if {[info exists idrangedrawn($id,$i)]} {
|
||||
drawlineseg $id $i
|
||||
}
|
||||
}
|
||||
foreach child [lindex $childlist $commitrow($id)] {
|
||||
foreach child $children($curview,$id) {
|
||||
if {[info exists iddrawn($child)]} {
|
||||
set row $commitrow($child)
|
||||
set row $commitrow($curview,$child)
|
||||
set col [lsearch -exact [lindex $rowidlist $row] $child]
|
||||
if {$col >= 0} {
|
||||
drawparentlinks $child $row $col [list $id]
|
||||
|
@ -1459,7 +2334,8 @@ proc drawcmittext {id row col rmx} {
|
|||
global commitlisted commitinfo rowidlist
|
||||
global rowtextx idpos idtags idheads idotherrefs
|
||||
global linehtag linentag linedtag
|
||||
global mainfont namefont canvxmax
|
||||
global mainfont canvxmax
|
||||
global hlview commitrow highlightedrows
|
||||
|
||||
set ofill [expr {[lindex $commitlisted $row]? "blue": "white"}]
|
||||
set x [xc $row $col]
|
||||
|
@ -1484,11 +2360,16 @@ proc drawcmittext {id row col rmx} {
|
|||
set name [lindex $commitinfo($id) 1]
|
||||
set date [lindex $commitinfo($id) 2]
|
||||
set date [formatdate $date]
|
||||
set font $mainfont
|
||||
if {[info exists hlview] && [info exists commitrow($hlview,$id)]} {
|
||||
lappend font bold
|
||||
lappend highlightedrows $row
|
||||
}
|
||||
set linehtag($row) [$canv create text $xt $y -anchor w \
|
||||
-text $headline -font $mainfont ]
|
||||
-text $headline -font $font]
|
||||
$canv bind $linehtag($row) <Button-3> "rowmenu %X %Y $id"
|
||||
set linentag($row) [$canv2 create text 3 $y -anchor w \
|
||||
-text $name -font $namefont]
|
||||
-text $name -font $mainfont]
|
||||
set linedtag($row) [$canv3 create text 3 $y -anchor w \
|
||||
-text $date -font $mainfont]
|
||||
set xr [expr {$xt + [font measure $mainfont $headline]}]
|
||||
|
@ -1500,14 +2381,14 @@ proc drawcmittext {id row col rmx} {
|
|||
|
||||
proc drawcmitrow {row} {
|
||||
global displayorder rowidlist
|
||||
global idrowranges idrangedrawn iddrawn
|
||||
global idrangedrawn iddrawn
|
||||
global commitinfo parentlist numcommits
|
||||
|
||||
if {$row >= $numcommits} return
|
||||
foreach id [lindex $rowidlist $row] {
|
||||
if {![info exists idrowranges($id)]} continue
|
||||
if {$id eq {}} continue
|
||||
set i -1
|
||||
foreach {s e} $idrowranges($id) {
|
||||
foreach {s e} [rowranges $id] {
|
||||
incr i
|
||||
if {$row < $s} continue
|
||||
if {$e eq {}} break
|
||||
|
@ -1576,62 +2457,90 @@ proc clear_display {} {
|
|||
catch {unset idrangedrawn}
|
||||
}
|
||||
|
||||
proc findcrossings {id} {
|
||||
global rowidlist parentlist numcommits rowoffsets displayorder
|
||||
|
||||
set cross {}
|
||||
set ccross {}
|
||||
foreach {s e} [rowranges $id] {
|
||||
if {$e >= $numcommits} {
|
||||
set e [expr {$numcommits - 1}]
|
||||
}
|
||||
if {$e <= $s} continue
|
||||
set x [lsearch -exact [lindex $rowidlist $e] $id]
|
||||
if {$x < 0} {
|
||||
puts "findcrossings: oops, no [shortids $id] in row $e"
|
||||
continue
|
||||
}
|
||||
for {set row $e} {[incr row -1] >= $s} {} {
|
||||
set olds [lindex $parentlist $row]
|
||||
set kid [lindex $displayorder $row]
|
||||
set kidx [lsearch -exact [lindex $rowidlist $row] $kid]
|
||||
if {$kidx < 0} continue
|
||||
set nextrow [lindex $rowidlist [expr {$row + 1}]]
|
||||
foreach p $olds {
|
||||
set px [lsearch -exact $nextrow $p]
|
||||
if {$px < 0} continue
|
||||
if {($kidx < $x && $x < $px) || ($px < $x && $x < $kidx)} {
|
||||
if {[lsearch -exact $ccross $p] >= 0} continue
|
||||
if {$x == $px + ($kidx < $px? -1: 1)} {
|
||||
lappend ccross $p
|
||||
} elseif {[lsearch -exact $cross $p] < 0} {
|
||||
lappend cross $p
|
||||
}
|
||||
}
|
||||
}
|
||||
set inc [lindex $rowoffsets $row $x]
|
||||
if {$inc eq {}} break
|
||||
incr x $inc
|
||||
}
|
||||
}
|
||||
return [concat $ccross {{}} $cross]
|
||||
}
|
||||
|
||||
proc assigncolor {id} {
|
||||
global colormap colors nextcolor
|
||||
global commitrow parentlist children childlist
|
||||
global cornercrossings crossings
|
||||
global commitrow parentlist children children curview
|
||||
|
||||
if {[info exists colormap($id)]} return
|
||||
set ncolors [llength $colors]
|
||||
if {[info exists commitrow($id)]} {
|
||||
set kids [lindex $childlist $commitrow($id)]
|
||||
} elseif {[info exists children($id)]} {
|
||||
set kids $children($id)
|
||||
if {[info exists children($curview,$id)]} {
|
||||
set kids $children($curview,$id)
|
||||
} else {
|
||||
set kids {}
|
||||
}
|
||||
if {[llength $kids] == 1} {
|
||||
set child [lindex $kids 0]
|
||||
if {[info exists colormap($child)]
|
||||
&& [llength [lindex $parentlist $commitrow($child)]] == 1} {
|
||||
&& [llength [lindex $parentlist $commitrow($curview,$child)]] == 1} {
|
||||
set colormap($id) $colormap($child)
|
||||
return
|
||||
}
|
||||
}
|
||||
set badcolors {}
|
||||
if {[info exists cornercrossings($id)]} {
|
||||
foreach x $cornercrossings($id) {
|
||||
if {[info exists colormap($x)]
|
||||
&& [lsearch -exact $badcolors $colormap($x)] < 0} {
|
||||
lappend badcolors $colormap($x)
|
||||
}
|
||||
set origbad {}
|
||||
foreach x [findcrossings $id] {
|
||||
if {$x eq {}} {
|
||||
# delimiter between corner crossings and other crossings
|
||||
if {[llength $badcolors] >= $ncolors - 1} break
|
||||
set origbad $badcolors
|
||||
}
|
||||
if {[llength $badcolors] >= $ncolors} {
|
||||
set badcolors {}
|
||||
if {[info exists colormap($x)]
|
||||
&& [lsearch -exact $badcolors $colormap($x)] < 0} {
|
||||
lappend badcolors $colormap($x)
|
||||
}
|
||||
}
|
||||
if {[llength $badcolors] >= $ncolors} {
|
||||
set badcolors $origbad
|
||||
}
|
||||
set origbad $badcolors
|
||||
if {[llength $badcolors] < $ncolors - 1} {
|
||||
if {[info exists crossings($id)]} {
|
||||
foreach x $crossings($id) {
|
||||
if {[info exists colormap($x)]
|
||||
&& [lsearch -exact $badcolors $colormap($x)] < 0} {
|
||||
lappend badcolors $colormap($x)
|
||||
}
|
||||
}
|
||||
if {[llength $badcolors] >= $ncolors} {
|
||||
set badcolors $origbad
|
||||
}
|
||||
}
|
||||
set origbad $badcolors
|
||||
}
|
||||
if {[llength $badcolors] < $ncolors - 1} {
|
||||
foreach child $kids {
|
||||
if {[info exists colormap($child)]
|
||||
&& [lsearch -exact $badcolors $colormap($child)] < 0} {
|
||||
lappend badcolors $colormap($child)
|
||||
}
|
||||
foreach p [lindex $parentlist $commitrow($child)] {
|
||||
foreach p [lindex $parentlist $commitrow($curview,$child)] {
|
||||
if {[info exists colormap($p)]
|
||||
&& [lsearch -exact $badcolors $colormap($p)] < 0} {
|
||||
lappend badcolors $colormap($p)
|
||||
|
@ -1664,7 +2573,7 @@ proc bindline {t id} {
|
|||
proc drawtags {id x xt y1} {
|
||||
global idtags idheads idotherrefs
|
||||
global linespc lthickness
|
||||
global canv mainfont commitrow rowtextx
|
||||
global canv mainfont commitrow rowtextx curview
|
||||
|
||||
set marks {}
|
||||
set ntags 0
|
||||
|
@ -1707,7 +2616,7 @@ proc drawtags {id x xt y1} {
|
|||
$xr $yt $xr $yb $xl $yb $x [expr {$yb - $delta}] \
|
||||
-width 1 -outline black -fill yellow -tags tag.$id]
|
||||
$canv bind $t <1> [list showtag $tag 1]
|
||||
set rowtextx($commitrow($id)) [expr {$xr + $linespc}]
|
||||
set rowtextx($commitrow($curview,$id)) [expr {$xr + $linespc}]
|
||||
} else {
|
||||
# draw a head or other ref
|
||||
if {[incr nheads -1] >= 0} {
|
||||
|
@ -1718,6 +2627,14 @@ proc drawtags {id x xt y1} {
|
|||
set xl [expr {$xl - $delta/2}]
|
||||
$canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
|
||||
-width 1 -outline black -fill $col -tags tag.$id
|
||||
if {[regexp {^(remotes/.*/|remotes/)} $tag match remoteprefix]} {
|
||||
set rwid [font measure $mainfont $remoteprefix]
|
||||
set xi [expr {$x + 1}]
|
||||
set yti [expr {$yt + 1}]
|
||||
set xri [expr {$x + $rwid}]
|
||||
$canv create polygon $xi $yti $xri $yti $xri $yb $xi $yb \
|
||||
-width 0 -fill "#ffddaa" -tags tag.$id
|
||||
}
|
||||
}
|
||||
set t [$canv create text $xl $y1 -anchor w -text $tag \
|
||||
-font $mainfont -tags tag.$id]
|
||||
|
@ -1728,55 +2645,6 @@ proc drawtags {id x xt y1} {
|
|||
return $xt
|
||||
}
|
||||
|
||||
proc checkcrossings {row endrow} {
|
||||
global displayorder parentlist rowidlist
|
||||
|
||||
for {} {$row < $endrow} {incr row} {
|
||||
set id [lindex $displayorder $row]
|
||||
set i [lsearch -exact [lindex $rowidlist $row] $id]
|
||||
if {$i < 0} continue
|
||||
set idlist [lindex $rowidlist [expr {$row+1}]]
|
||||
foreach p [lindex $parentlist $row] {
|
||||
set j [lsearch -exact $idlist $p]
|
||||
if {$j > 0} {
|
||||
if {$j < $i - 1} {
|
||||
notecrossings $row $p $j $i [expr {$j+1}]
|
||||
} elseif {$j > $i + 1} {
|
||||
notecrossings $row $p $i $j [expr {$j-1}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
proc notecrossings {row id lo hi corner} {
|
||||
global rowidlist crossings cornercrossings
|
||||
|
||||
for {set i $lo} {[incr i] < $hi} {} {
|
||||
set p [lindex [lindex $rowidlist $row] $i]
|
||||
if {$p == {}} continue
|
||||
if {$i == $corner} {
|
||||
if {![info exists cornercrossings($id)]
|
||||
|| [lsearch -exact $cornercrossings($id) $p] < 0} {
|
||||
lappend cornercrossings($id) $p
|
||||
}
|
||||
if {![info exists cornercrossings($p)]
|
||||
|| [lsearch -exact $cornercrossings($p) $id] < 0} {
|
||||
lappend cornercrossings($p) $id
|
||||
}
|
||||
} else {
|
||||
if {![info exists crossings($id)]
|
||||
|| [lsearch -exact $crossings($id) $p] < 0} {
|
||||
lappend crossings($id) $p
|
||||
}
|
||||
if {![info exists crossings($p)]
|
||||
|| [lsearch -exact $crossings($p) $id] < 0} {
|
||||
lappend crossings($p) $id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
proc xcoord {i level ln} {
|
||||
global canvx0 xspc1 xspc2
|
||||
|
||||
|
@ -1789,23 +2657,25 @@ proc xcoord {i level ln} {
|
|||
return $x
|
||||
}
|
||||
|
||||
proc finishcommits {} {
|
||||
global commitidx phase
|
||||
global canv mainfont ctext maincursor textcursor
|
||||
global findinprogress
|
||||
proc show_status {msg} {
|
||||
global canv mainfont
|
||||
|
||||
if {$commitidx > 0} {
|
||||
clear_display
|
||||
$canv create text 3 3 -anchor nw -text $msg -font $mainfont -tags textitems
|
||||
}
|
||||
|
||||
proc finishcommits {} {
|
||||
global commitidx phase curview
|
||||
global canv mainfont ctext maincursor textcursor
|
||||
global findinprogress pending_select
|
||||
|
||||
if {$commitidx($curview) > 0} {
|
||||
drawrest
|
||||
} else {
|
||||
$canv delete all
|
||||
$canv create text 3 3 -anchor nw -text "No commits selected" \
|
||||
-font $mainfont -tags textitems
|
||||
}
|
||||
if {![info exists findinprogress]} {
|
||||
. config -cursor $maincursor
|
||||
settextcursor $textcursor
|
||||
show_status "No commits selected"
|
||||
}
|
||||
set phase {}
|
||||
catch {unset pending_select}
|
||||
}
|
||||
|
||||
# Don't change the text pane cursor if it is currently the hand cursor,
|
||||
|
@ -1819,17 +2689,41 @@ proc settextcursor {c} {
|
|||
set curtextcursor $c
|
||||
}
|
||||
|
||||
proc nowbusy {what} {
|
||||
global isbusy
|
||||
|
||||
if {[array names isbusy] eq {}} {
|
||||
. config -cursor watch
|
||||
settextcursor watch
|
||||
}
|
||||
set isbusy($what) 1
|
||||
}
|
||||
|
||||
proc notbusy {what} {
|
||||
global isbusy maincursor textcursor
|
||||
|
||||
catch {unset isbusy($what)}
|
||||
if {[array names isbusy] eq {}} {
|
||||
. config -cursor $maincursor
|
||||
settextcursor $textcursor
|
||||
}
|
||||
}
|
||||
|
||||
proc drawrest {} {
|
||||
global numcommits
|
||||
global startmsecs
|
||||
global canvy0 numcommits linespc
|
||||
global rowlaidout commitidx
|
||||
global rowlaidout commitidx curview
|
||||
global pending_select
|
||||
|
||||
set row $rowlaidout
|
||||
layoutrows $rowlaidout $commitidx 1
|
||||
layoutrows $rowlaidout $commitidx($curview) 1
|
||||
layouttail
|
||||
optimize_rows $row 0 $commitidx
|
||||
showstuff $commitidx
|
||||
optimize_rows $row 0 $commitidx($curview)
|
||||
showstuff $commitidx($curview)
|
||||
if {[info exists pending_select]} {
|
||||
selectline 0 1
|
||||
}
|
||||
|
||||
set drawmsecs [expr {[clock clicks -milliseconds] - $startmsecs}]
|
||||
#puts "overall $drawmsecs ms for $numcommits commits"
|
||||
|
@ -1858,7 +2752,7 @@ proc findmatches {f} {
|
|||
proc dofind {} {
|
||||
global findtype findloc findstring markedmatches commitinfo
|
||||
global numcommits displayorder linehtag linentag linedtag
|
||||
global mainfont namefont canv canv2 canv3 selectedline
|
||||
global mainfont canv canv2 canv3 selectedline
|
||||
global matchinglines foundstring foundstrlen matchstring
|
||||
global commitdata
|
||||
|
||||
|
@ -1919,7 +2813,7 @@ proc dofind {} {
|
|||
markmatches $canv $l $f $linehtag($l) $matches $mainfont
|
||||
} elseif {$ty == "Author"} {
|
||||
drawcmitrow $l
|
||||
markmatches $canv2 $l $f $linentag($l) $matches $namefont
|
||||
markmatches $canv2 $l $f $linentag($l) $matches $mainfont
|
||||
} elseif {$ty == "Date"} {
|
||||
drawcmitrow $l
|
||||
markmatches $canv3 $l $f $linedtag($l) $matches $mainfont
|
||||
|
@ -2017,13 +2911,8 @@ proc stopfindproc {{done 0}} {
|
|||
catch {close $findprocfile}
|
||||
unset findprocpid
|
||||
}
|
||||
if {[info exists findinprogress]} {
|
||||
unset findinprogress
|
||||
if {$phase != "incrdraw"} {
|
||||
. config -cursor $maincursor
|
||||
settextcursor $textcursor
|
||||
}
|
||||
}
|
||||
catch {unset findinprogress}
|
||||
notbusy find
|
||||
}
|
||||
|
||||
proc findpatches {} {
|
||||
|
@ -2063,14 +2952,13 @@ proc findpatches {} {
|
|||
fconfigure $f -blocking 0
|
||||
fileevent $f readable readfindproc
|
||||
set finddidsel 0
|
||||
. config -cursor watch
|
||||
settextcursor watch
|
||||
nowbusy find
|
||||
set findinprogress 1
|
||||
}
|
||||
|
||||
proc readfindproc {} {
|
||||
global findprocfile finddidsel
|
||||
global commitrow matchinglines findinsertpos
|
||||
global commitrow matchinglines findinsertpos curview
|
||||
|
||||
set n [gets $findprocfile line]
|
||||
if {$n < 0} {
|
||||
|
@ -2087,11 +2975,11 @@ proc readfindproc {} {
|
|||
stopfindproc
|
||||
return
|
||||
}
|
||||
if {![info exists commitrow($id)]} {
|
||||
if {![info exists commitrow($curview,$id)]} {
|
||||
puts stderr "spurious id: $id"
|
||||
return
|
||||
}
|
||||
set l $commitrow($id)
|
||||
set l $commitrow($curview,$id)
|
||||
insertmatch $l $id
|
||||
}
|
||||
|
||||
|
@ -2165,8 +3053,7 @@ proc findfiles {} {
|
|||
set finddidsel 0
|
||||
set findinsertpos end
|
||||
set id [lindex $displayorder $l]
|
||||
. config -cursor watch
|
||||
settextcursor watch
|
||||
nowbusy find
|
||||
set findinprogress 1
|
||||
findcont
|
||||
update
|
||||
|
@ -2336,7 +3223,7 @@ proc commit_descriptor {p} {
|
|||
# append some text to the ctext widget, and make any SHA1 ID
|
||||
# that we know about be a clickable link.
|
||||
proc appendwithlinks {text} {
|
||||
global ctext commitrow linknum
|
||||
global ctext commitrow linknum curview
|
||||
|
||||
set start [$ctext index "end - 1c"]
|
||||
$ctext insert end $text
|
||||
|
@ -2346,11 +3233,12 @@ proc appendwithlinks {text} {
|
|||
set s [lindex $l 0]
|
||||
set e [lindex $l 1]
|
||||
set linkid [string range $text $s $e]
|
||||
if {![info exists commitrow($linkid)]} continue
|
||||
if {![info exists commitrow($curview,$linkid)]} continue
|
||||
incr e
|
||||
$ctext tag add link "$start + $s c" "$start + $e c"
|
||||
$ctext tag add link$linknum "$start + $s c" "$start + $e c"
|
||||
$ctext tag bind link$linknum <1> [list selectline $commitrow($linkid) 1]
|
||||
$ctext tag bind link$linknum <1> \
|
||||
[list selectline $commitrow($curview,$linkid) 1]
|
||||
incr linknum
|
||||
}
|
||||
$ctext tag conf link -foreground blue -underline 1
|
||||
|
@ -2378,10 +3266,12 @@ proc selectline {l isnew} {
|
|||
global canv canv2 canv3 ctext commitinfo selectedline
|
||||
global displayorder linehtag linentag linedtag
|
||||
global canvy0 linespc parentlist childlist
|
||||
global cflist currentid sha1entry
|
||||
global currentid sha1entry
|
||||
global commentend idtags linknum
|
||||
global mergemax numcommits
|
||||
global mergemax numcommits pending_select
|
||||
global cmitmode
|
||||
|
||||
catch {unset pending_select}
|
||||
$canv delete hover
|
||||
normalline
|
||||
if {$l < 0 || $l >= $numcommits} return
|
||||
|
@ -2451,8 +3341,6 @@ proc selectline {l isnew} {
|
|||
$ctext conf -state normal
|
||||
$ctext delete 0.0 end
|
||||
set linknum 0
|
||||
$ctext mark set fmark.0 0.0
|
||||
$ctext mark gravity fmark.0 left
|
||||
set info $commitinfo($id)
|
||||
set date [formatdate [lindex $info 2]]
|
||||
$ctext insert end "Author: [lindex $info 1] $date\n"
|
||||
|
@ -2500,9 +3388,10 @@ proc selectline {l isnew} {
|
|||
$ctext conf -state disabled
|
||||
set commentend [$ctext index "end - 1c"]
|
||||
|
||||
$cflist delete 0 end
|
||||
$cflist insert end "Comments"
|
||||
if {[llength $olds] <= 1} {
|
||||
init_flist "Comments"
|
||||
if {$cmitmode eq "tree"} {
|
||||
gettree $id
|
||||
} elseif {[llength $olds] <= 1} {
|
||||
startdiff $id
|
||||
} else {
|
||||
mergediff $id $l
|
||||
|
@ -2549,24 +3438,34 @@ proc selnextpage {dir} {
|
|||
}
|
||||
|
||||
proc unselectline {} {
|
||||
global selectedline
|
||||
global selectedline currentid
|
||||
|
||||
catch {unset selectedline}
|
||||
catch {unset currentid}
|
||||
allcanvs delete secsel
|
||||
}
|
||||
|
||||
proc addtohistory {cmd} {
|
||||
global history historyindex
|
||||
proc reselectline {} {
|
||||
global selectedline
|
||||
|
||||
if {[info exists selectedline]} {
|
||||
selectline $selectedline 0
|
||||
}
|
||||
}
|
||||
|
||||
proc addtohistory {cmd} {
|
||||
global history historyindex curview
|
||||
|
||||
set elt [list $curview $cmd]
|
||||
if {$historyindex > 0
|
||||
&& [lindex $history [expr {$historyindex - 1}]] == $cmd} {
|
||||
&& [lindex $history [expr {$historyindex - 1}]] == $elt} {
|
||||
return
|
||||
}
|
||||
|
||||
if {$historyindex < [llength $history]} {
|
||||
set history [lreplace $history $historyindex end $cmd]
|
||||
set history [lreplace $history $historyindex end $elt]
|
||||
} else {
|
||||
lappend history $cmd
|
||||
lappend history $elt
|
||||
}
|
||||
incr historyindex
|
||||
if {$historyindex > 1} {
|
||||
|
@ -2577,13 +3476,23 @@ proc addtohistory {cmd} {
|
|||
.ctop.top.bar.rightbut conf -state disabled
|
||||
}
|
||||
|
||||
proc godo {elt} {
|
||||
global curview
|
||||
|
||||
set view [lindex $elt 0]
|
||||
set cmd [lindex $elt 1]
|
||||
if {$curview != $view} {
|
||||
showview $view
|
||||
}
|
||||
eval $cmd
|
||||
}
|
||||
|
||||
proc goback {} {
|
||||
global history historyindex
|
||||
|
||||
if {$historyindex > 1} {
|
||||
incr historyindex -1
|
||||
set cmd [lindex $history [expr {$historyindex - 1}]]
|
||||
eval $cmd
|
||||
godo [lindex $history [expr {$historyindex - 1}]]
|
||||
.ctop.top.bar.rightbut conf -state normal
|
||||
}
|
||||
if {$historyindex <= 1} {
|
||||
|
@ -2597,7 +3506,7 @@ proc goforw {} {
|
|||
if {$historyindex < [llength $history]} {
|
||||
set cmd [lindex $history $historyindex]
|
||||
incr historyindex
|
||||
eval $cmd
|
||||
godo $cmd
|
||||
.ctop.top.bar.leftbut conf -state normal
|
||||
}
|
||||
if {$historyindex >= [llength $history]} {
|
||||
|
@ -2605,14 +3514,101 @@ proc goforw {} {
|
|||
}
|
||||
}
|
||||
|
||||
proc gettree {id} {
|
||||
global treefilelist treeidlist diffids diffmergeid treepending
|
||||
|
||||
set diffids $id
|
||||
catch {unset diffmergeid}
|
||||
if {![info exists treefilelist($id)]} {
|
||||
if {![info exists treepending]} {
|
||||
if {[catch {set gtf [open [concat | git-ls-tree -r $id] r]}]} {
|
||||
return
|
||||
}
|
||||
set treepending $id
|
||||
set treefilelist($id) {}
|
||||
set treeidlist($id) {}
|
||||
fconfigure $gtf -blocking 0
|
||||
fileevent $gtf readable [list gettreeline $gtf $id]
|
||||
}
|
||||
} else {
|
||||
setfilelist $id
|
||||
}
|
||||
}
|
||||
|
||||
proc gettreeline {gtf id} {
|
||||
global treefilelist treeidlist treepending cmitmode diffids
|
||||
|
||||
while {[gets $gtf line] >= 0} {
|
||||
if {[lindex $line 1] ne "blob"} continue
|
||||
set sha1 [lindex $line 2]
|
||||
set fname [lindex $line 3]
|
||||
lappend treefilelist($id) $fname
|
||||
lappend treeidlist($id) $sha1
|
||||
}
|
||||
if {![eof $gtf]} return
|
||||
close $gtf
|
||||
unset treepending
|
||||
if {$cmitmode ne "tree"} {
|
||||
if {![info exists diffmergeid]} {
|
||||
gettreediffs $diffids
|
||||
}
|
||||
} elseif {$id ne $diffids} {
|
||||
gettree $diffids
|
||||
} else {
|
||||
setfilelist $id
|
||||
}
|
||||
}
|
||||
|
||||
proc showfile {f} {
|
||||
global treefilelist treeidlist diffids
|
||||
global ctext commentend
|
||||
|
||||
set i [lsearch -exact $treefilelist($diffids) $f]
|
||||
if {$i < 0} {
|
||||
puts "oops, $f not in list for id $diffids"
|
||||
return
|
||||
}
|
||||
set blob [lindex $treeidlist($diffids) $i]
|
||||
if {[catch {set bf [open [concat | git-cat-file blob $blob] r]} err]} {
|
||||
puts "oops, error reading blob $blob: $err"
|
||||
return
|
||||
}
|
||||
fconfigure $bf -blocking 0
|
||||
fileevent $bf readable [list getblobline $bf $diffids]
|
||||
$ctext config -state normal
|
||||
$ctext delete $commentend end
|
||||
$ctext insert end "\n"
|
||||
$ctext insert end "$f\n" filesep
|
||||
$ctext config -state disabled
|
||||
$ctext yview $commentend
|
||||
}
|
||||
|
||||
proc getblobline {bf id} {
|
||||
global diffids cmitmode ctext
|
||||
|
||||
if {$id ne $diffids || $cmitmode ne "tree"} {
|
||||
catch {close $bf}
|
||||
return
|
||||
}
|
||||
$ctext config -state normal
|
||||
while {[gets $bf line] >= 0} {
|
||||
$ctext insert end "$line\n"
|
||||
}
|
||||
if {[eof $bf]} {
|
||||
# delete last newline
|
||||
$ctext delete "end - 2c" "end - 1c"
|
||||
close $bf
|
||||
}
|
||||
$ctext config -state disabled
|
||||
}
|
||||
|
||||
proc mergediff {id l} {
|
||||
global diffmergeid diffopts mdifffd
|
||||
global difffilestart diffids
|
||||
global diffids
|
||||
global parentlist
|
||||
|
||||
set diffmergeid $id
|
||||
set diffids $id
|
||||
catch {unset difffilestart}
|
||||
# this doesn't seem to actually affect anything...
|
||||
set env(GIT_DIFF_OPTS) $diffopts
|
||||
set cmd [concat | git-diff-tree --no-commit-id --cc $id]
|
||||
|
@ -2647,11 +3643,8 @@ proc getmergediffline {mdf id np} {
|
|||
# start of a new file
|
||||
$ctext insert end "\n"
|
||||
set here [$ctext index "end - 1c"]
|
||||
set i [$cflist index end]
|
||||
$ctext mark set fmark.$i $here
|
||||
$ctext mark gravity fmark.$i left
|
||||
set difffilestart([expr {$i-1}]) $here
|
||||
$cflist insert end $fname
|
||||
lappend difffilestart $here
|
||||
add_flist [list $fname]
|
||||
set l [expr {(78 - [string length $fname]) / 2}]
|
||||
set pad [string range "----------------------------------------" 1 $l]
|
||||
$ctext insert end "$pad $fname $pad\n" filesep
|
||||
|
@ -2721,9 +3714,7 @@ proc startdiff {ids} {
|
|||
|
||||
proc addtocflist {ids} {
|
||||
global treediffs cflist
|
||||
foreach f $treediffs($ids) {
|
||||
$cflist insert end $f
|
||||
}
|
||||
add_flist $treediffs($ids)
|
||||
getblobdiffs $ids
|
||||
}
|
||||
|
||||
|
@ -2740,6 +3731,7 @@ proc gettreediffs {ids} {
|
|||
|
||||
proc gettreediffline {gdtf ids} {
|
||||
global treediff treediffs treepending diffids diffmergeid
|
||||
global cmitmode
|
||||
|
||||
set n [gets $gdtf line]
|
||||
if {$n < 0} {
|
||||
|
@ -2747,7 +3739,9 @@ proc gettreediffline {gdtf ids} {
|
|||
close $gdtf
|
||||
set treediffs($ids) $treediff
|
||||
unset treepending
|
||||
if {$ids != $diffids} {
|
||||
if {$cmitmode eq "tree"} {
|
||||
gettree $diffids
|
||||
} elseif {$ids != $diffids} {
|
||||
if {![info exists diffmergeid]} {
|
||||
gettreediffs $diffids
|
||||
}
|
||||
|
@ -2762,7 +3756,7 @@ proc gettreediffline {gdtf ids} {
|
|||
|
||||
proc getblobdiffs {ids} {
|
||||
global diffopts blobdifffd diffids env curdifftag curtagstart
|
||||
global difffilestart nextupdate diffinhdr treediffs
|
||||
global nextupdate diffinhdr treediffs
|
||||
|
||||
set env(GIT_DIFF_OPTS) $diffopts
|
||||
set cmd [concat | git-diff-tree --no-commit-id -r -p -C $ids]
|
||||
|
@ -2775,11 +3769,23 @@ proc getblobdiffs {ids} {
|
|||
set blobdifffd($ids) $bdf
|
||||
set curdifftag Comments
|
||||
set curtagstart 0.0
|
||||
catch {unset difffilestart}
|
||||
fileevent $bdf readable [list getblobdiffline $bdf $diffids]
|
||||
set nextupdate [expr {[clock clicks -milliseconds] + 100}]
|
||||
}
|
||||
|
||||
proc setinlist {var i val} {
|
||||
global $var
|
||||
|
||||
while {[llength [set $var]] < $i} {
|
||||
lappend $var {}
|
||||
}
|
||||
if {[llength [set $var]] == $i} {
|
||||
lappend $var $val
|
||||
} else {
|
||||
lset $var $i $val
|
||||
}
|
||||
}
|
||||
|
||||
proc getblobdiffline {bdf ids} {
|
||||
global diffids blobdifffd ctext curdifftag curtagstart
|
||||
global diffnexthead diffnextnote difffilestart
|
||||
|
@ -2803,23 +3809,17 @@ proc getblobdiffline {bdf ids} {
|
|||
# start of a new file
|
||||
$ctext insert end "\n"
|
||||
$ctext tag add $curdifftag $curtagstart end
|
||||
set curtagstart [$ctext index "end - 1c"]
|
||||
set header $newname
|
||||
set here [$ctext index "end - 1c"]
|
||||
set i [lsearch -exact $treediffs($diffids) $fname]
|
||||
set curtagstart $here
|
||||
set header $newname
|
||||
set i [lsearch -exact $treediffs($ids) $fname]
|
||||
if {$i >= 0} {
|
||||
set difffilestart($i) $here
|
||||
incr i
|
||||
$ctext mark set fmark.$i $here
|
||||
$ctext mark gravity fmark.$i left
|
||||
setinlist difffilestart $i $here
|
||||
}
|
||||
if {$newname != $fname} {
|
||||
set i [lsearch -exact $treediffs($diffids) $newname]
|
||||
if {$newname ne $fname} {
|
||||
set i [lsearch -exact $treediffs($ids) $newname]
|
||||
if {$i >= 0} {
|
||||
set difffilestart($i) $here
|
||||
incr i
|
||||
$ctext mark set fmark.$i $here
|
||||
$ctext mark gravity fmark.$i left
|
||||
setinlist difffilestart $i $here
|
||||
}
|
||||
}
|
||||
set curdifftag "f:$fname"
|
||||
|
@ -2869,26 +3869,11 @@ proc getblobdiffline {bdf ids} {
|
|||
proc nextfile {} {
|
||||
global difffilestart ctext
|
||||
set here [$ctext index @0,0]
|
||||
for {set i 0} {[info exists difffilestart($i)]} {incr i} {
|
||||
if {[$ctext compare $difffilestart($i) > $here]} {
|
||||
if {![info exists pos]
|
||||
|| [$ctext compare $difffilestart($i) < $pos]} {
|
||||
set pos $difffilestart($i)
|
||||
}
|
||||
foreach loc $difffilestart {
|
||||
if {[$ctext compare $loc > $here]} {
|
||||
$ctext yview $loc
|
||||
}
|
||||
}
|
||||
if {[info exists pos]} {
|
||||
$ctext yview $pos
|
||||
}
|
||||
}
|
||||
|
||||
proc listboxsel {} {
|
||||
global ctext cflist currentid
|
||||
if {![info exists currentid]} return
|
||||
set sel [lsort [$cflist curselection]]
|
||||
if {$sel eq {}} return
|
||||
set first [lindex $sel 0]
|
||||
catch {$ctext yview fmark.$first}
|
||||
}
|
||||
|
||||
proc setcoords {} {
|
||||
|
@ -2921,11 +3906,10 @@ proc redisplay {} {
|
|||
}
|
||||
|
||||
proc incrfont {inc} {
|
||||
global mainfont namefont textfont ctext canv phase
|
||||
global mainfont textfont ctext canv phase
|
||||
global stopped entries
|
||||
unmarkmatches
|
||||
set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
|
||||
set namefont [lreplace $namefont 1 1 [expr {[lindex $namefont 1] + $inc}]]
|
||||
set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
|
||||
setcoords
|
||||
$ctext conf -font $textfont
|
||||
|
@ -2933,7 +3917,7 @@ proc incrfont {inc} {
|
|||
foreach e $entries {
|
||||
$e conf -font $mainfont
|
||||
}
|
||||
if {$phase == "getcommits"} {
|
||||
if {$phase eq "getcommits"} {
|
||||
$canv itemconf textitems -font $mainfont
|
||||
}
|
||||
redisplay
|
||||
|
@ -2964,7 +3948,7 @@ proc sha1change {n1 n2 op} {
|
|||
|
||||
proc gotocommit {} {
|
||||
global sha1string currentid commitrow tagids headids
|
||||
global displayorder numcommits
|
||||
global displayorder numcommits curview
|
||||
|
||||
if {$sha1string == {}
|
||||
|| ([info exists currentid] && $sha1string == $currentid)} return
|
||||
|
@ -2990,8 +3974,8 @@ proc gotocommit {} {
|
|||
}
|
||||
}
|
||||
}
|
||||
if {[info exists commitrow($id)]} {
|
||||
selectline $commitrow($id) 1
|
||||
if {[info exists commitrow($curview,$id)]} {
|
||||
selectline $commitrow($curview,$id) 1
|
||||
return
|
||||
}
|
||||
if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
|
||||
|
@ -3066,12 +4050,13 @@ proc linehover {} {
|
|||
}
|
||||
|
||||
proc clickisonarrow {id y} {
|
||||
global lthickness idrowranges
|
||||
global lthickness
|
||||
|
||||
set ranges [rowranges $id]
|
||||
set thresh [expr {2 * $lthickness + 6}]
|
||||
set n [expr {[llength $idrowranges($id)] - 1}]
|
||||
set n [expr {[llength $ranges] - 1}]
|
||||
for {set i 1} {$i < $n} {incr i} {
|
||||
set row [lindex $idrowranges($id) $i]
|
||||
set row [lindex $ranges $i]
|
||||
if {abs([yc $row] - $y) < $thresh} {
|
||||
return $i
|
||||
}
|
||||
|
@ -3080,11 +4065,11 @@ proc clickisonarrow {id y} {
|
|||
}
|
||||
|
||||
proc arrowjump {id n y} {
|
||||
global idrowranges canv
|
||||
global canv
|
||||
|
||||
# 1 <-> 2, 3 <-> 4, etc...
|
||||
set n [expr {(($n - 1) ^ 1) + 1}]
|
||||
set row [lindex $idrowranges($id) $n]
|
||||
set row [lindex [rowranges $id] $n]
|
||||
set yt [yc $row]
|
||||
set ymax [lindex [$canv cget -scrollregion] 3]
|
||||
if {$ymax eq {} || $ymax <= 0} return
|
||||
|
@ -3098,7 +4083,7 @@ proc arrowjump {id n y} {
|
|||
}
|
||||
|
||||
proc lineclick {x y id isnew} {
|
||||
global ctext commitinfo childlist commitrow cflist canv thickerline
|
||||
global ctext commitinfo children canv thickerline curview
|
||||
|
||||
if {![info exists commitinfo($id)] && ![getcommit $id]} return
|
||||
unmarkmatches
|
||||
|
@ -3137,7 +4122,7 @@ proc lineclick {x y id isnew} {
|
|||
$ctext insert end "\tAuthor:\t[lindex $info 1]\n"
|
||||
set date [formatdate [lindex $info 2]]
|
||||
$ctext insert end "\tDate:\t$date\n"
|
||||
set kids [lindex $childlist $commitrow($id)]
|
||||
set kids $children($curview,$id)
|
||||
if {$kids ne {}} {
|
||||
$ctext insert end "\nChildren:"
|
||||
set i 0
|
||||
|
@ -3155,8 +4140,7 @@ proc lineclick {x y id isnew} {
|
|||
}
|
||||
}
|
||||
$ctext conf -state disabled
|
||||
|
||||
$cflist delete 0 end
|
||||
init_flist {}
|
||||
}
|
||||
|
||||
proc normalline {} {
|
||||
|
@ -3169,9 +4153,9 @@ proc normalline {} {
|
|||
}
|
||||
|
||||
proc selbyid {id} {
|
||||
global commitrow
|
||||
if {[info exists commitrow($id)]} {
|
||||
selectline $commitrow($id) 1
|
||||
global commitrow curview
|
||||
if {[info exists commitrow($curview,$id)]} {
|
||||
selectline $commitrow($curview,$id) 1
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3184,9 +4168,10 @@ proc mstime {} {
|
|||
}
|
||||
|
||||
proc rowmenu {x y id} {
|
||||
global rowctxmenu commitrow selectedline rowmenuid
|
||||
global rowctxmenu commitrow selectedline rowmenuid curview
|
||||
|
||||
if {![info exists selectedline] || $commitrow($id) eq $selectedline} {
|
||||
if {![info exists selectedline]
|
||||
|| $commitrow($curview,$id) eq $selectedline} {
|
||||
set state disabled
|
||||
} else {
|
||||
set state normal
|
||||
|
@ -3214,15 +4199,12 @@ proc diffvssel {dirn} {
|
|||
}
|
||||
|
||||
proc doseldiff {oldid newid} {
|
||||
global ctext cflist
|
||||
global ctext
|
||||
global commitinfo
|
||||
|
||||
$ctext conf -state normal
|
||||
$ctext delete 0.0 end
|
||||
$ctext mark set fmark.0 0.0
|
||||
$ctext mark gravity fmark.0 left
|
||||
$cflist delete 0 end
|
||||
$cflist insert end "Top"
|
||||
init_flist "Top"
|
||||
$ctext insert end "From "
|
||||
$ctext tag conf link -foreground blue -underline 1
|
||||
$ctext tag bind link <Enter> { %W configure -cursor hand2 }
|
||||
|
@ -3389,14 +4371,15 @@ proc domktag {} {
|
|||
}
|
||||
|
||||
proc redrawtags {id} {
|
||||
global canv linehtag commitrow idpos selectedline
|
||||
global canv linehtag commitrow idpos selectedline curview
|
||||
|
||||
if {![info exists commitrow($id)]} return
|
||||
drawcmitrow $commitrow($id)
|
||||
if {![info exists commitrow($curview,$id)]} return
|
||||
drawcmitrow $commitrow($curview,$id)
|
||||
$canv delete tag.$id
|
||||
set xt [eval drawtags $id $idpos($id)]
|
||||
$canv coords $linehtag($commitrow($id)) $xt [lindex $idpos($id) 2]
|
||||
if {[info exists selectedline] && $selectedline == $commitrow($id)} {
|
||||
$canv coords $linehtag($commitrow($curview,$id)) $xt [lindex $idpos($id) 2]
|
||||
if {[info exists selectedline]
|
||||
&& $selectedline == $commitrow($curview,$id)} {
|
||||
selectline $selectedline 0
|
||||
}
|
||||
}
|
||||
|
@ -3508,7 +4491,7 @@ proc rereadrefs {} {
|
|||
}
|
||||
|
||||
proc showtag {tag isnew} {
|
||||
global ctext cflist tagcontents tagids linknum
|
||||
global ctext tagcontents tagids linknum
|
||||
|
||||
if {$isnew} {
|
||||
addtohistory [list showtag $tag 0]
|
||||
|
@ -3523,7 +4506,7 @@ proc showtag {tag isnew} {
|
|||
}
|
||||
appendwithlinks $text
|
||||
$ctext conf -state disabled
|
||||
$cflist delete 0 end
|
||||
init_flist {}
|
||||
}
|
||||
|
||||
proc doquit {} {
|
||||
|
@ -3905,13 +4888,13 @@ set fastdate 0
|
|||
set uparrowlen 7
|
||||
set downarrowlen 7
|
||||
set mingaplen 30
|
||||
set flistmode "flat"
|
||||
set cmitmode "patch"
|
||||
|
||||
set colors {green red blue magenta darkgrey brown orange}
|
||||
|
||||
catch {source ~/.gitk}
|
||||
|
||||
set namefont $mainfont
|
||||
|
||||
font create optionfont -family sans-serif -size -12
|
||||
|
||||
set revtreeargs {}
|
||||
|
@ -3928,19 +4911,77 @@ foreach arg $argv {
|
|||
# check that we can find a .git directory somewhere...
|
||||
set gitdir [gitdir]
|
||||
if {![file isdirectory $gitdir]} {
|
||||
error_popup "Cannot find the git directory \"$gitdir\"."
|
||||
show_error . "Cannot find the git directory \"$gitdir\"."
|
||||
exit 1
|
||||
}
|
||||
|
||||
set cmdline_files {}
|
||||
set i [lsearch -exact $revtreeargs "--"]
|
||||
if {$i >= 0} {
|
||||
set cmdline_files [lrange $revtreeargs [expr {$i + 1}] end]
|
||||
set revtreeargs [lrange $revtreeargs 0 [expr {$i - 1}]]
|
||||
} elseif {$revtreeargs ne {}} {
|
||||
if {[catch {
|
||||
set f [eval exec git-rev-parse --no-revs --no-flags $revtreeargs]
|
||||
set cmdline_files [split $f "\n"]
|
||||
set n [llength $cmdline_files]
|
||||
set revtreeargs [lrange $revtreeargs 0 end-$n]
|
||||
} err]} {
|
||||
# unfortunately we get both stdout and stderr in $err,
|
||||
# so look for "fatal:".
|
||||
set i [string first "fatal:" $err]
|
||||
if {$i > 0} {
|
||||
set err [string range [expr {$i + 6}] end]
|
||||
}
|
||||
show_error . "Bad arguments to gitk:\n$err"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
set history {}
|
||||
set historyindex 0
|
||||
|
||||
set optim_delay 16
|
||||
|
||||
set nextviewnum 1
|
||||
set curview 0
|
||||
set selectedview 0
|
||||
set selectedhlview {}
|
||||
set viewfiles(0) {}
|
||||
set viewperm(0) 0
|
||||
set viewargs(0) {}
|
||||
|
||||
set cmdlineok 0
|
||||
set stopped 0
|
||||
set stuffsaved 0
|
||||
set patchnum 0
|
||||
setcoords
|
||||
makewindow $revtreeargs
|
||||
makewindow
|
||||
readrefs
|
||||
getcommits $revtreeargs
|
||||
|
||||
if {$cmdline_files ne {} || $revtreeargs ne {}} {
|
||||
# create a view for the files/dirs specified on the command line
|
||||
set curview 1
|
||||
set selectedview 1
|
||||
set nextviewnum 2
|
||||
set viewname(1) "Command line"
|
||||
set viewfiles(1) $cmdline_files
|
||||
set viewargs(1) $revtreeargs
|
||||
set viewperm(1) 0
|
||||
addviewmenu 1
|
||||
.bar.view entryconf 2 -state normal
|
||||
.bar.view entryconf 3 -state normal
|
||||
}
|
||||
|
||||
if {[info exists permviews]} {
|
||||
foreach v $permviews {
|
||||
set n $nextviewnum
|
||||
incr nextviewnum
|
||||
set viewname($n) [lindex $v 0]
|
||||
set viewfiles($n) [lindex $v 1]
|
||||
set viewargs($n) [lindex $v 2]
|
||||
set viewperm($n) 1
|
||||
addviewmenu $n
|
||||
}
|
||||
}
|
||||
getcommits
|
||||
|
|
16
log-tree.c
16
log-tree.c
|
@ -3,6 +3,15 @@
|
|||
#include "commit.h"
|
||||
#include "log-tree.h"
|
||||
|
||||
static void show_parents(struct commit *commit, int abbrev)
|
||||
{
|
||||
struct commit_list *p;
|
||||
for (p = commit->parents; p ; p = p->next) {
|
||||
struct commit *parent = p->item;
|
||||
printf(" %s", diff_unique_abbrev(parent->object.sha1, abbrev));
|
||||
}
|
||||
}
|
||||
|
||||
void show_log(struct rev_info *opt, struct log_info *log, const char *sep)
|
||||
{
|
||||
static char this_header[16384];
|
||||
|
@ -15,7 +24,10 @@ void show_log(struct rev_info *opt, struct log_info *log, const char *sep)
|
|||
|
||||
opt->loginfo = NULL;
|
||||
if (!opt->verbose_header) {
|
||||
puts(sha1_to_hex(commit->object.sha1));
|
||||
fputs(diff_unique_abbrev(commit->object.sha1, abbrev_commit), stdout);
|
||||
if (opt->parents)
|
||||
show_parents(commit, abbrev_commit);
|
||||
putchar('\n');
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -57,6 +69,8 @@ void show_log(struct rev_info *opt, struct log_info *log, const char *sep)
|
|||
printf("%s%s",
|
||||
opt->commit_format == CMIT_FMT_ONELINE ? "" : "commit ",
|
||||
diff_unique_abbrev(commit->object.sha1, abbrev_commit));
|
||||
if (opt->parents)
|
||||
show_parents(commit, abbrev_commit);
|
||||
if (parent)
|
||||
printf(" (from %s)",
|
||||
diff_unique_abbrev(parent->object.sha1,
|
||||
|
|
|
@ -24,16 +24,14 @@ static const char *sha1_to_hex_zero(const unsigned char *sha1)
|
|||
|
||||
static void resolve(const char *base, struct name_entry *branch1, struct name_entry *result)
|
||||
{
|
||||
char branch1_sha1[50];
|
||||
|
||||
/* If it's already branch1, don't bother showing it */
|
||||
if (!branch1)
|
||||
return;
|
||||
memcpy(branch1_sha1, sha1_to_hex_zero(branch1->sha1), 41);
|
||||
|
||||
printf("0 %06o->%06o %s->%s %s%s\n",
|
||||
branch1->mode, result->mode,
|
||||
branch1_sha1, sha1_to_hex_zero(result->sha1),
|
||||
sha1_to_hex_zero(branch1->sha1),
|
||||
sha1_to_hex_zero(result->sha1),
|
||||
base, result->path);
|
||||
}
|
||||
|
||||
|
|
|
@ -29,12 +29,12 @@ static int verify_packfile(struct packed_git *p)
|
|||
pack_base = p->pack_base;
|
||||
SHA1_Update(&ctx, pack_base, pack_size - 20);
|
||||
SHA1_Final(sha1, &ctx);
|
||||
if (memcmp(sha1, index_base + index_size - 40, 20))
|
||||
return error("Packfile %s SHA1 mismatch with idx",
|
||||
p->pack_name);
|
||||
if (memcmp(sha1, pack_base + pack_size - 20, 20))
|
||||
return error("Packfile %s SHA1 mismatch with itself",
|
||||
p->pack_name);
|
||||
if (memcmp(sha1, index_base + index_size - 40, 20))
|
||||
return error("Packfile %s SHA1 mismatch with idx",
|
||||
p->pack_name);
|
||||
|
||||
/* Make sure everything reachable from idx is valid. Since we
|
||||
* have verified that nr_objects matches between idx and pack,
|
||||
|
|
|
@ -1032,12 +1032,6 @@ static int try_delta(struct unpacked *cur, struct unpacked *old, unsigned max_de
|
|||
max_depth -= cur_entry->delta_limit;
|
||||
}
|
||||
|
||||
size = cur_entry->size;
|
||||
oldsize = old_entry->size;
|
||||
sizediff = oldsize > size ? oldsize - size : size - oldsize;
|
||||
|
||||
if (size < 50)
|
||||
return -1;
|
||||
if (old_entry->depth >= max_depth)
|
||||
return 0;
|
||||
|
||||
|
@ -1048,9 +1042,12 @@ static int try_delta(struct unpacked *cur, struct unpacked *old, unsigned max_de
|
|||
* more space-efficient (deletes don't have to say _what_ they
|
||||
* delete).
|
||||
*/
|
||||
size = cur_entry->size;
|
||||
max_size = size / 2 - 20;
|
||||
if (cur_entry->delta)
|
||||
max_size = cur_entry->delta_size-1;
|
||||
oldsize = old_entry->size;
|
||||
sizediff = oldsize < size ? size - oldsize : 0;
|
||||
if (sizediff >= max_size)
|
||||
return 0;
|
||||
delta_buf = diff_delta(old->data, oldsize,
|
||||
|
@ -1109,6 +1106,9 @@ static void find_deltas(struct object_entry **list, int window, int depth)
|
|||
*/
|
||||
continue;
|
||||
|
||||
if (entry->size < 50)
|
||||
continue;
|
||||
|
||||
free(n->data);
|
||||
n->entry = entry;
|
||||
n->data = read_sha1_file(entry->sha1, type, &size);
|
||||
|
@ -1239,6 +1239,7 @@ int main(int argc, char **argv)
|
|||
|
||||
setup_git_directory();
|
||||
|
||||
progress = isatty(2);
|
||||
for (i = 1; i < argc; i++) {
|
||||
const char *arg = argv[i];
|
||||
|
||||
|
@ -1269,6 +1270,10 @@ int main(int argc, char **argv)
|
|||
usage(pack_usage);
|
||||
continue;
|
||||
}
|
||||
if (!strcmp("--progress", arg)) {
|
||||
progress = 1;
|
||||
continue;
|
||||
}
|
||||
if (!strcmp("-q", arg)) {
|
||||
progress = 0;
|
||||
continue;
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
#include "delta.h"
|
||||
|
||||
void *patch_delta(void *src_buf, unsigned long src_size,
|
||||
void *delta_buf, unsigned long delta_size,
|
||||
const void *delta_buf, unsigned long delta_size,
|
||||
unsigned long *dst_size)
|
||||
{
|
||||
const unsigned char *data, *top;
|
||||
|
|
4
refs.c
4
refs.c
|
@ -76,8 +76,8 @@ int create_symref(const char *git_HEAD, const char *refs_heads_master)
|
|||
char ref[1000];
|
||||
int fd, len, written;
|
||||
|
||||
#ifdef USE_SYMLINK_HEAD
|
||||
if (!only_use_symrefs) {
|
||||
#ifndef NO_SYMLINK_HEAD
|
||||
if (prefer_symlink_refs) {
|
||||
unlink(git_HEAD);
|
||||
if (!symlink(refs_heads_master, git_HEAD))
|
||||
return 0;
|
||||
|
|
|
@ -2,46 +2,63 @@
|
|||
#include <regex.h>
|
||||
|
||||
static const char git_config_set_usage[] =
|
||||
"git-repo-config [ --bool | --int ] [--get | --get-all | --replace-all | --unset | --unset-all] name [value [value_regex]]";
|
||||
"git-repo-config [ --bool | --int ] [--get | --get-all | --get-regexp | --replace-all | --unset | --unset-all] name [value [value_regex]] | --list";
|
||||
|
||||
static char* key = NULL;
|
||||
static char* value = NULL;
|
||||
static regex_t* key_regexp = NULL;
|
||||
static regex_t* regexp = NULL;
|
||||
static int show_keys = 0;
|
||||
static int use_key_regexp = 0;
|
||||
static int do_all = 0;
|
||||
static int do_not_match = 0;
|
||||
static int seen = 0;
|
||||
static enum { T_RAW, T_INT, T_BOOL } type = T_RAW;
|
||||
|
||||
static int show_all_config(const char *key_, const char *value_)
|
||||
{
|
||||
if (value_)
|
||||
printf("%s=%s\n", key_, value_);
|
||||
else
|
||||
printf("%s\n", key_);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int show_config(const char* key_, const char* value_)
|
||||
{
|
||||
char value[256];
|
||||
const char *vptr = value;
|
||||
int dup_error = 0;
|
||||
|
||||
if (value_ == NULL)
|
||||
value_ = "";
|
||||
|
||||
if (!strcmp(key_, key) &&
|
||||
(regexp == NULL ||
|
||||
if (!use_key_regexp && strcmp(key_, key))
|
||||
return 0;
|
||||
if (use_key_regexp && regexec(key_regexp, key_, 0, NULL, 0))
|
||||
return 0;
|
||||
if (regexp != NULL &&
|
||||
(do_not_match ^
|
||||
!regexec(regexp, value_, 0, NULL, 0)))) {
|
||||
if (do_all) {
|
||||
printf("%s\n", value_);
|
||||
return 0;
|
||||
}
|
||||
if (seen > 0) {
|
||||
fprintf(stderr, "More than one value: %s\n", value);
|
||||
free(value);
|
||||
}
|
||||
regexec(regexp, value_, 0, NULL, 0)))
|
||||
return 0;
|
||||
|
||||
if (type == T_INT) {
|
||||
value = malloc(256);
|
||||
sprintf(value, "%d", git_config_int(key_, value_));
|
||||
} else if (type == T_BOOL) {
|
||||
value = malloc(256);
|
||||
sprintf(value, "%s", git_config_bool(key_, value_)
|
||||
? "true" : "false");
|
||||
} else {
|
||||
value = strdup(value_);
|
||||
}
|
||||
seen++;
|
||||
if (show_keys)
|
||||
printf("%s ", key_);
|
||||
if (seen && !do_all)
|
||||
dup_error = 1;
|
||||
if (type == T_INT)
|
||||
sprintf(value, "%d", git_config_int(key_, value_));
|
||||
else if (type == T_BOOL)
|
||||
vptr = git_config_bool(key_, value_) ? "true" : "false";
|
||||
else
|
||||
vptr = value_;
|
||||
seen++;
|
||||
if (dup_error) {
|
||||
error("More than one value for the key %s: %s",
|
||||
key_, vptr);
|
||||
}
|
||||
else
|
||||
printf("%s\n", vptr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -54,6 +71,14 @@ static int get_value(const char* key_, const char* regex_)
|
|||
key[i] = tolower(key_[i]);
|
||||
key[i] = 0;
|
||||
|
||||
if (use_key_regexp) {
|
||||
key_regexp = (regex_t*)malloc(sizeof(regex_t));
|
||||
if (regcomp(key_regexp, key, REG_EXTENDED)) {
|
||||
fprintf(stderr, "Invalid key pattern: %s\n", key_);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (regex_) {
|
||||
if (regex_[0] == '!') {
|
||||
do_not_match = 1;
|
||||
|
@ -67,11 +92,7 @@ static int get_value(const char* key_, const char* regex_)
|
|||
}
|
||||
}
|
||||
|
||||
i = git_config(show_config);
|
||||
if (value) {
|
||||
printf("%s\n", value);
|
||||
free(value);
|
||||
}
|
||||
git_config(show_config);
|
||||
free(key);
|
||||
if (regexp) {
|
||||
regfree(regexp);
|
||||
|
@ -79,9 +100,9 @@ static int get_value(const char* key_, const char* regex_)
|
|||
}
|
||||
|
||||
if (do_all)
|
||||
return 0;
|
||||
return !seen;
|
||||
|
||||
return seen == 1 ? 0 : 1;
|
||||
return (seen == 1) ? 0 : 1;
|
||||
}
|
||||
|
||||
int main(int argc, const char **argv)
|
||||
|
@ -93,6 +114,8 @@ int main(int argc, const char **argv)
|
|||
type = T_INT;
|
||||
else if (!strcmp(argv[1], "--bool"))
|
||||
type = T_BOOL;
|
||||
else if (!strcmp(argv[1], "--list") || !strcmp(argv[1], "-l"))
|
||||
return git_config(show_all_config);
|
||||
else
|
||||
break;
|
||||
argc--;
|
||||
|
@ -112,6 +135,11 @@ int main(int argc, const char **argv)
|
|||
else if (!strcmp(argv[1], "--get-all")) {
|
||||
do_all = 1;
|
||||
return get_value(argv[2], NULL);
|
||||
} else if (!strcmp(argv[1], "--get-regexp")) {
|
||||
show_keys = 1;
|
||||
use_key_regexp = 1;
|
||||
do_all = 1;
|
||||
return get_value(argv[2], NULL);
|
||||
} else
|
||||
|
||||
return git_config_set(argv[1], argv[2]);
|
||||
|
@ -125,6 +153,11 @@ int main(int argc, const char **argv)
|
|||
else if (!strcmp(argv[1], "--get-all")) {
|
||||
do_all = 1;
|
||||
return get_value(argv[2], argv[3]);
|
||||
} else if (!strcmp(argv[1], "--get-regexp")) {
|
||||
show_keys = 1;
|
||||
use_key_regexp = 1;
|
||||
do_all = 1;
|
||||
return get_value(argv[2], argv[3]);
|
||||
} else if (!strcmp(argv[1], "--replace-all"))
|
||||
|
||||
return git_config_set_multivar(argv[2], argv[3], NULL, 1);
|
||||
|
|
|
@ -169,14 +169,12 @@ int main(int argc, char **argv)
|
|||
git_config(git_default_config);
|
||||
|
||||
for (i = 1; i < argc; i++) {
|
||||
struct stat st;
|
||||
char *arg = argv[i];
|
||||
char *dotdot;
|
||||
|
||||
if (as_is) {
|
||||
if (show_file(arg) && as_is < 2)
|
||||
if (lstat(arg, &st) < 0)
|
||||
die("'%s': %s", arg, strerror(errno));
|
||||
verify_filename(prefix, arg);
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(arg,"-n")) {
|
||||
|
@ -342,8 +340,7 @@ int main(int argc, char **argv)
|
|||
continue;
|
||||
if (verify)
|
||||
die("Needed a single revision");
|
||||
if (lstat(arg, &st) < 0)
|
||||
die("'%s': %s", arg, strerror(errno));
|
||||
verify_filename(prefix, arg);
|
||||
}
|
||||
show_default();
|
||||
if (verify && revs_count != 1)
|
||||
|
|
65
revision.c
65
revision.c
|
@ -477,6 +477,36 @@ static void handle_all(struct rev_info *revs, unsigned flags)
|
|||
for_each_ref(handle_one_ref);
|
||||
}
|
||||
|
||||
static int add_parents_only(struct rev_info *revs, const char *arg, int flags)
|
||||
{
|
||||
unsigned char sha1[20];
|
||||
struct object *it;
|
||||
struct commit *commit;
|
||||
struct commit_list *parents;
|
||||
|
||||
if (*arg == '^') {
|
||||
flags ^= UNINTERESTING;
|
||||
arg++;
|
||||
}
|
||||
if (get_sha1(arg, sha1))
|
||||
return 0;
|
||||
while (1) {
|
||||
it = get_reference(revs, arg, sha1, 0);
|
||||
if (strcmp(it->type, tag_type))
|
||||
break;
|
||||
memcpy(sha1, ((struct tag*)it)->tagged->sha1, 20);
|
||||
}
|
||||
if (strcmp(it->type, commit_type))
|
||||
return 0;
|
||||
commit = (struct commit *)it;
|
||||
for (parents = commit->parents; parents; parents = parents->next) {
|
||||
it = &parents->item->object;
|
||||
it->flags |= flags;
|
||||
add_pending_object(revs, it, arg);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void init_revisions(struct rev_info *revs)
|
||||
{
|
||||
memset(revs, 0, sizeof(*revs));
|
||||
|
@ -544,7 +574,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
|
|||
revs->max_count = atoi(arg + 12);
|
||||
continue;
|
||||
}
|
||||
/* accept -<digit>, like traditilnal "head" */
|
||||
/* accept -<digit>, like traditional "head" */
|
||||
if ((*arg == '-') && isdigit(arg[1])) {
|
||||
revs->max_count = atoi(arg + 1);
|
||||
continue;
|
||||
|
@ -664,6 +694,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
|
|||
}
|
||||
if (!strcmp(arg, "-c")) {
|
||||
revs->diff = 1;
|
||||
revs->dense_combined_merges = 0;
|
||||
revs->combine_merges = 1;
|
||||
continue;
|
||||
}
|
||||
|
@ -740,32 +771,49 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
|
|||
include = get_reference(revs, next, sha1, flags);
|
||||
if (!exclude || !include)
|
||||
die("Invalid revision range %s..%s", arg, next);
|
||||
|
||||
if (!seen_dashdash) {
|
||||
*dotdot = '.';
|
||||
verify_non_filename(revs->prefix, arg);
|
||||
}
|
||||
add_pending_object(revs, exclude, this);
|
||||
add_pending_object(revs, include, next);
|
||||
continue;
|
||||
}
|
||||
*dotdot = '.';
|
||||
}
|
||||
dotdot = strstr(arg, "^@");
|
||||
if (dotdot && !dotdot[2]) {
|
||||
*dotdot = 0;
|
||||
if (add_parents_only(revs, arg, flags))
|
||||
continue;
|
||||
*dotdot = '^';
|
||||
}
|
||||
local_flags = 0;
|
||||
if (*arg == '^') {
|
||||
local_flags = UNINTERESTING;
|
||||
arg++;
|
||||
}
|
||||
if (get_sha1(arg, sha1) < 0) {
|
||||
struct stat st;
|
||||
int j;
|
||||
|
||||
if (seen_dashdash || local_flags)
|
||||
die("bad revision '%s'", arg);
|
||||
|
||||
/* If we didn't have a "--", all filenames must exist */
|
||||
for (j = i; j < argc; j++) {
|
||||
if (lstat(argv[j], &st) < 0)
|
||||
die("'%s': %s", argv[j], strerror(errno));
|
||||
}
|
||||
/* If we didn't have a "--":
|
||||
* (1) all filenames must exist;
|
||||
* (2) all rev-args must not be interpretable
|
||||
* as a valid filename.
|
||||
* but the latter we have checked in the main loop.
|
||||
*/
|
||||
for (j = i; j < argc; j++)
|
||||
verify_filename(revs->prefix, argv[j]);
|
||||
|
||||
revs->prune_data = get_pathspec(revs->prefix, argv + i);
|
||||
break;
|
||||
}
|
||||
if (!seen_dashdash)
|
||||
verify_non_filename(revs->prefix, arg);
|
||||
object = get_reference(revs, arg, sha1, flags ^ local_flags);
|
||||
add_pending_object(revs, object, arg);
|
||||
}
|
||||
|
@ -789,7 +837,8 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
|
|||
}
|
||||
if (revs->combine_merges) {
|
||||
revs->ignore_merges = 0;
|
||||
if (revs->dense_combined_merges)
|
||||
if (revs->dense_combined_merges &&
|
||||
(revs->diffopt.output_format != DIFF_FORMAT_DIFFSTAT))
|
||||
revs->diffopt.output_format = DIFF_FORMAT_PATCH;
|
||||
}
|
||||
revs->diffopt.abbrev = revs->abbrev;
|
||||
|
|
43
setup.c
43
setup.c
|
@ -62,6 +62,49 @@ const char *prefix_filename(const char *pfx, int pfx_len, const char *arg)
|
|||
return path;
|
||||
}
|
||||
|
||||
/*
|
||||
* Verify a filename that we got as an argument for a pathspec
|
||||
* entry. Note that a filename that begins with "-" never verifies
|
||||
* as true, because even if such a filename were to exist, we want
|
||||
* it to be preceded by the "--" marker (or we want the user to
|
||||
* use a format like "./-filename")
|
||||
*/
|
||||
void verify_filename(const char *prefix, const char *arg)
|
||||
{
|
||||
const char *name;
|
||||
struct stat st;
|
||||
|
||||
if (*arg == '-')
|
||||
die("bad flag '%s' used after filename", arg);
|
||||
name = prefix ? prefix_filename(prefix, strlen(prefix), arg) : arg;
|
||||
if (!lstat(name, &st))
|
||||
return;
|
||||
if (errno == ENOENT)
|
||||
die("ambiguous argument '%s': unknown revision or path not in the working tree.\n"
|
||||
"Use '--' to separate paths from revisions", arg);
|
||||
die("'%s': %s", arg, strerror(errno));
|
||||
}
|
||||
|
||||
/*
|
||||
* Opposite of the above: the command line did not have -- marker
|
||||
* and we parsed the arg as a refname. It should not be interpretable
|
||||
* as a filename.
|
||||
*/
|
||||
void verify_non_filename(const char *prefix, const char *arg)
|
||||
{
|
||||
const char *name;
|
||||
struct stat st;
|
||||
|
||||
if (*arg == '-')
|
||||
return; /* flag */
|
||||
name = prefix ? prefix_filename(prefix, strlen(prefix), arg) : arg;
|
||||
if (!lstat(name, &st))
|
||||
die("ambiguous argument '%s': both revision and filename\n"
|
||||
"Use '--' to separate filenames from revisions", arg);
|
||||
if (errno != ENOENT)
|
||||
die("'%s': %s", arg, strerror(errno));
|
||||
}
|
||||
|
||||
const char **get_pathspec(const char *prefix, const char **pathspec)
|
||||
{
|
||||
const char *entry = *pathspec;
|
||||
|
|
|
@ -108,9 +108,10 @@ int safe_create_leading_directories(char *path)
|
|||
|
||||
char * sha1_to_hex(const unsigned char *sha1)
|
||||
{
|
||||
static char buffer[50];
|
||||
static int bufno;
|
||||
static char hexbuffer[4][50];
|
||||
static const char hex[] = "0123456789abcdef";
|
||||
char *buf = buffer;
|
||||
char *buffer = hexbuffer[3 & ++bufno], *buf = buffer;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 20; i++) {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
#include "refs.h"
|
||||
|
||||
static const char show_branch_usage[] =
|
||||
"git-show-branch [--current] [--all] [--heads] [--tags] [--topo-order] [--more=count | --list | --independent | --merge-base ] [--topics] [<refs>...]";
|
||||
"git-show-branch [--dense] [--current] [--all] [--heads] [--tags] [--topo-order] [--more=count | --list | --independent | --merge-base ] [--topics] [<refs>...]";
|
||||
|
||||
static int default_num = 0;
|
||||
static int default_alloc = 0;
|
||||
|
@ -527,6 +527,27 @@ static int git_show_branch_config(const char *var, const char *value)
|
|||
return git_default_config(var, value);
|
||||
}
|
||||
|
||||
static int omit_in_dense(struct commit *commit, struct commit **rev, int n)
|
||||
{
|
||||
/* If the commit is tip of the named branches, do not
|
||||
* omit it.
|
||||
* Otherwise, if it is a merge that is reachable from only one
|
||||
* tip, it is not that interesting.
|
||||
*/
|
||||
int i, flag, count;
|
||||
for (i = 0; i < n; i++)
|
||||
if (rev[i] == commit)
|
||||
return 0;
|
||||
flag = commit->object.flags;
|
||||
for (i = count = 0; i < n; i++) {
|
||||
if (flag & (1u << (i + REV_SHIFT)))
|
||||
count++;
|
||||
}
|
||||
if (count == 1)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int ac, char **av)
|
||||
{
|
||||
struct commit *rev[MAX_REVS], *commit;
|
||||
|
@ -548,6 +569,7 @@ int main(int ac, char **av)
|
|||
int with_current_branch = 0;
|
||||
int head_at = -1;
|
||||
int topics = 0;
|
||||
int dense = 1;
|
||||
|
||||
setup_git_directory();
|
||||
git_config(git_show_branch_config);
|
||||
|
@ -590,6 +612,8 @@ int main(int ac, char **av)
|
|||
lifo = 1;
|
||||
else if (!strcmp(arg, "--topics"))
|
||||
topics = 1;
|
||||
else if (!strcmp(arg, "--sparse"))
|
||||
dense = 0;
|
||||
else if (!strcmp(arg, "--date-order"))
|
||||
lifo = 0;
|
||||
else
|
||||
|
@ -732,12 +756,15 @@ int main(int ac, char **av)
|
|||
shown_merge_point |= is_merge_point;
|
||||
|
||||
if (1 < num_rev) {
|
||||
int is_merge = !!(commit->parents && commit->parents->next);
|
||||
int is_merge = !!(commit->parents &&
|
||||
commit->parents->next);
|
||||
if (topics &&
|
||||
!is_merge_point &&
|
||||
(this_flag & (1u << REV_SHIFT)))
|
||||
continue;
|
||||
|
||||
if (dense && is_merge &&
|
||||
omit_in_dense(commit, rev, num_rev))
|
||||
continue;
|
||||
for (i = 0; i < num_rev; i++) {
|
||||
int mark;
|
||||
if (!(this_flag & (1u << (i + REV_SHIFT))))
|
||||
|
|
|
@ -174,6 +174,27 @@ test_expect_success \
|
|||
'git-ls-tree -r output for a known tree.' \
|
||||
'diff current expected'
|
||||
|
||||
# But with -r -t we can have both.
|
||||
test_expect_success \
|
||||
'showing tree with git-ls-tree -r -t' \
|
||||
'git-ls-tree -r -t $tree >current'
|
||||
cat >expected <<\EOF
|
||||
100644 blob f87290f8eb2cbbea7857214459a0739927eab154 path0
|
||||
120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01 path0sym
|
||||
040000 tree 58a09c23e2ca152193f2786e06986b7b6712bdbe path2
|
||||
100644 blob 3feff949ed00a62d9f7af97c15cd8a30595e7ac7 path2/file2
|
||||
120000 blob d8ce161addc5173867a3c3c730924388daedbc38 path2/file2sym
|
||||
040000 tree 21ae8269cacbe57ae09138dcc3a2887f904d02b3 path3
|
||||
100644 blob 0aa34cae68d0878578ad119c86ca2b5ed5b28376 path3/file3
|
||||
120000 blob 8599103969b43aff7e430efea79ca4636466794f path3/file3sym
|
||||
040000 tree 3c5e5399f3a333eddecce7a9b9465b63f65f51e2 path3/subp3
|
||||
100644 blob 00fb5908cb97c2564a9783c0c64087333b3b464f path3/subp3/file3
|
||||
120000 blob 6649a1ebe9e9f1c553b66f5a6e74136a07ccc57c path3/subp3/file3sym
|
||||
EOF
|
||||
test_expect_success \
|
||||
'git-ls-tree -r output for a known tree.' \
|
||||
'diff current expected'
|
||||
|
||||
################################################################
|
||||
rm .git/index
|
||||
test_expect_success \
|
||||
|
@ -205,4 +226,32 @@ test_expect_success \
|
|||
'no diff after checkout and git-update-index --refresh.' \
|
||||
'git-diff-files >current && cmp -s current /dev/null'
|
||||
|
||||
################################################################
|
||||
P=087704a96baf1c2d1c869a8b084481e121c88b5b
|
||||
test_expect_success \
|
||||
'git-commit-tree records the correct tree in a commit.' \
|
||||
'commit0=$(echo NO | git-commit-tree $P) &&
|
||||
tree=$(git show --pretty=raw $commit0 |
|
||||
sed -n -e "s/^tree //p" -e "/^author /q") &&
|
||||
test "z$tree" = "z$P"'
|
||||
|
||||
test_expect_success \
|
||||
'git-commit-tree records the correct parent in a commit.' \
|
||||
'commit1=$(echo NO | git-commit-tree $P -p $commit0) &&
|
||||
parent=$(git show --pretty=raw $commit1 |
|
||||
sed -n -e "s/^parent //p" -e "/^author /q") &&
|
||||
test "z$commit0" = "z$parent"'
|
||||
|
||||
test_expect_success \
|
||||
'git-commit-tree omits duplicated parent in a commit.' \
|
||||
'commit2=$(echo NO | git-commit-tree $P -p $commit0 -p $commit0) &&
|
||||
parent=$(git show --pretty=raw $commit2 |
|
||||
sed -n -e "s/^parent //p" -e "/^author /q" |
|
||||
sort -u) &&
|
||||
test "z$commit0" = "z$parent" &&
|
||||
numparent=$(git show --pretty=raw $commit2 |
|
||||
sed -n -e "s/^parent //p" -e "/^author /q" |
|
||||
wc -l) &&
|
||||
test $numparent = 1'
|
||||
|
||||
test_done
|
||||
|
|
|
@ -37,7 +37,7 @@ compare_change () {
|
|||
}
|
||||
|
||||
check_cache_at () {
|
||||
clean_if_empty=`git-diff-files "$1"`
|
||||
clean_if_empty=`git-diff-files -- "$1"`
|
||||
case "$clean_if_empty" in
|
||||
'') echo "$1: clean" ;;
|
||||
?*) echo "$1: dirty" ;;
|
||||
|
|
|
@ -20,7 +20,7 @@ compare_change () {
|
|||
}
|
||||
|
||||
check_cache_at () {
|
||||
clean_if_empty=`git-diff-files "$1"`
|
||||
clean_if_empty=`git-diff-files -- "$1"`
|
||||
case "$clean_if_empty" in
|
||||
'') echo "$1: clean" ;;
|
||||
?*) echo "$1: dirty" ;;
|
||||
|
|
|
@ -247,6 +247,24 @@ EOF
|
|||
|
||||
test_expect_success 'hierarchical section value' 'cmp .git/config expect'
|
||||
|
||||
cat > expect << EOF
|
||||
beta.noindent=sillyValue
|
||||
nextsection.nonewline=wow2 for me
|
||||
123456.a123=987
|
||||
1.2.3.alpha=beta
|
||||
EOF
|
||||
|
||||
test_expect_success 'working --list' \
|
||||
'git-repo-config --list > output && cmp output expect'
|
||||
|
||||
cat > expect << EOF
|
||||
beta.noindent sillyValue
|
||||
nextsection.nonewline wow2 for me
|
||||
EOF
|
||||
|
||||
test_expect_success '--get-regexp' \
|
||||
'git-repo-config --get-regexp in > output && cmp output expect'
|
||||
|
||||
cat > .git/config << EOF
|
||||
[novalue]
|
||||
variable
|
||||
|
@ -255,5 +273,41 @@ EOF
|
|||
test_expect_success 'get variable with no value' \
|
||||
'git-repo-config --get novalue.variable ^$'
|
||||
|
||||
git-repo-config > output 2>&1
|
||||
|
||||
test_expect_success 'no arguments, but no crash' \
|
||||
"test $? = 129 && grep usage output"
|
||||
|
||||
cat > .git/config << EOF
|
||||
[a.b]
|
||||
c = d
|
||||
EOF
|
||||
|
||||
git-repo-config a.x y
|
||||
|
||||
cat > expect << EOF
|
||||
[a.b]
|
||||
c = d
|
||||
[a]
|
||||
x = y
|
||||
EOF
|
||||
|
||||
test_expect_success 'new section is partial match of another' 'cmp .git/config expect'
|
||||
|
||||
git-repo-config b.x y
|
||||
git-repo-config a.b c
|
||||
|
||||
cat > expect << EOF
|
||||
[a.b]
|
||||
c = d
|
||||
[a]
|
||||
x = y
|
||||
b = c
|
||||
[b]
|
||||
x = y
|
||||
EOF
|
||||
|
||||
test_expect_success 'new variable inserts into proper section' 'cmp .git/config expect'
|
||||
|
||||
test_done
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ cat >expected <<\EOF
|
|||
EOF
|
||||
test_expect_success \
|
||||
'limit to path should show nothing' \
|
||||
'git-diff-index --cached $tree path >current &&
|
||||
'git-diff-index --cached $tree -- path >current &&
|
||||
compare_diff_raw current expected'
|
||||
|
||||
cat >expected <<\EOF
|
||||
|
@ -36,7 +36,7 @@ cat >expected <<\EOF
|
|||
EOF
|
||||
test_expect_success \
|
||||
'limit to path1 should show path1/file1' \
|
||||
'git-diff-index --cached $tree path1 >current &&
|
||||
'git-diff-index --cached $tree -- path1 >current &&
|
||||
compare_diff_raw current expected'
|
||||
|
||||
cat >expected <<\EOF
|
||||
|
@ -44,7 +44,7 @@ cat >expected <<\EOF
|
|||
EOF
|
||||
test_expect_success \
|
||||
'limit to path1/ should show path1/file1' \
|
||||
'git-diff-index --cached $tree path1/ >current &&
|
||||
'git-diff-index --cached $tree -- path1/ >current &&
|
||||
compare_diff_raw current expected'
|
||||
|
||||
cat >expected <<\EOF
|
||||
|
@ -52,14 +52,14 @@ cat >expected <<\EOF
|
|||
EOF
|
||||
test_expect_success \
|
||||
'limit to file0 should show file0' \
|
||||
'git-diff-index --cached $tree file0 >current &&
|
||||
'git-diff-index --cached $tree -- file0 >current &&
|
||||
compare_diff_raw current expected'
|
||||
|
||||
cat >expected <<\EOF
|
||||
EOF
|
||||
test_expect_success \
|
||||
'limit to file0/ should emit nothing.' \
|
||||
'git-diff-index --cached $tree file0/ >current &&
|
||||
'git-diff-index --cached $tree -- file0/ >current &&
|
||||
compare_diff_raw current expected'
|
||||
|
||||
test_done
|
||||
|
|
170
update-index.c
170
update-index.c
|
@ -6,6 +6,7 @@
|
|||
#include "cache.h"
|
||||
#include "strbuf.h"
|
||||
#include "quote.h"
|
||||
#include "tree-walk.h"
|
||||
|
||||
/*
|
||||
* Default to not allowing changes to the list of files. The
|
||||
|
@ -328,7 +329,7 @@ static int add_cacheinfo(unsigned int mode, const unsigned char *sha1,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int chmod_path(int flip, const char *path)
|
||||
static void chmod_path(int flip, const char *path)
|
||||
{
|
||||
int pos;
|
||||
struct cache_entry *ce;
|
||||
|
@ -336,21 +337,24 @@ static int chmod_path(int flip, const char *path)
|
|||
|
||||
pos = cache_name_pos(path, strlen(path));
|
||||
if (pos < 0)
|
||||
return -1;
|
||||
goto fail;
|
||||
ce = active_cache[pos];
|
||||
mode = ntohl(ce->ce_mode);
|
||||
if (!S_ISREG(mode))
|
||||
return -1;
|
||||
goto fail;
|
||||
switch (flip) {
|
||||
case '+':
|
||||
ce->ce_mode |= htonl(0111); break;
|
||||
case '-':
|
||||
ce->ce_mode &= htonl(~0111); break;
|
||||
default:
|
||||
return -1;
|
||||
goto fail;
|
||||
}
|
||||
active_cache_changed = 1;
|
||||
return 0;
|
||||
report("chmod %cx '%s'", flip, path);
|
||||
return;
|
||||
fail:
|
||||
die("git-update-index: cannot chmod %cx '%s'", flip, path);
|
||||
}
|
||||
|
||||
static struct cache_file cache_file;
|
||||
|
@ -360,23 +364,26 @@ static void update_one(const char *path, const char *prefix, int prefix_length)
|
|||
const char *p = prefix_path(prefix, prefix_length, path);
|
||||
if (!verify_path(p)) {
|
||||
fprintf(stderr, "Ignoring path %s\n", path);
|
||||
return;
|
||||
goto free_return;
|
||||
}
|
||||
if (mark_valid_only) {
|
||||
if (mark_valid(p))
|
||||
die("Unable to mark file %s", path);
|
||||
return;
|
||||
goto free_return;
|
||||
}
|
||||
|
||||
if (force_remove) {
|
||||
if (remove_file_from_cache(p))
|
||||
die("git-update-index: unable to remove %s", path);
|
||||
report("remove '%s'", path);
|
||||
return;
|
||||
goto free_return;
|
||||
}
|
||||
if (add_file_to_cache(p))
|
||||
die("Unable to process file %s", path);
|
||||
report("add '%s'", path);
|
||||
free_return:
|
||||
if (p != path)
|
||||
free((char*)p);
|
||||
}
|
||||
|
||||
static void read_index_info(int line_termination)
|
||||
|
@ -469,7 +476,129 @@ static void read_index_info(int line_termination)
|
|||
}
|
||||
|
||||
static const char update_index_usage[] =
|
||||
"git-update-index [-q] [--add] [--replace] [--remove] [--unmerged] [--refresh] [--cacheinfo] [--chmod=(+|-)x] [--info-only] [--force-remove] [--stdin] [--index-info] [--ignore-missing] [-z] [--verbose] [--] <file>...";
|
||||
"git-update-index [-q] [--add] [--replace] [--remove] [--unmerged] [--refresh] [--really-refresh] [--cacheinfo] [--chmod=(+|-)x] [--assume-unchanged] [--info-only] [--force-remove] [--stdin] [--index-info] [--unresolve] [--ignore-missing] [-z] [--verbose] [--] <file>...";
|
||||
|
||||
static unsigned char head_sha1[20];
|
||||
static unsigned char merge_head_sha1[20];
|
||||
|
||||
static struct cache_entry *read_one_ent(const char *which,
|
||||
unsigned char *ent, const char *path,
|
||||
int namelen, int stage)
|
||||
{
|
||||
unsigned mode;
|
||||
unsigned char sha1[20];
|
||||
int size;
|
||||
struct cache_entry *ce;
|
||||
|
||||
if (get_tree_entry(ent, path, sha1, &mode)) {
|
||||
error("%s: not in %s branch.", path, which);
|
||||
return NULL;
|
||||
}
|
||||
if (mode == S_IFDIR) {
|
||||
error("%s: not a blob in %s branch.", path, which);
|
||||
return NULL;
|
||||
}
|
||||
size = cache_entry_size(namelen);
|
||||
ce = xcalloc(1, size);
|
||||
|
||||
memcpy(ce->sha1, sha1, 20);
|
||||
memcpy(ce->name, path, namelen);
|
||||
ce->ce_flags = create_ce_flags(namelen, stage);
|
||||
ce->ce_mode = create_ce_mode(mode);
|
||||
return ce;
|
||||
}
|
||||
|
||||
static int unresolve_one(const char *path)
|
||||
{
|
||||
int namelen = strlen(path);
|
||||
int pos;
|
||||
int ret = 0;
|
||||
struct cache_entry *ce_2 = NULL, *ce_3 = NULL;
|
||||
|
||||
/* See if there is such entry in the index. */
|
||||
pos = cache_name_pos(path, namelen);
|
||||
if (pos < 0) {
|
||||
/* If there isn't, either it is unmerged, or
|
||||
* resolved as "removed" by mistake. We do not
|
||||
* want to do anything in the former case.
|
||||
*/
|
||||
pos = -pos-1;
|
||||
if (pos < active_nr) {
|
||||
struct cache_entry *ce = active_cache[pos];
|
||||
if (ce_namelen(ce) == namelen &&
|
||||
!memcmp(ce->name, path, namelen)) {
|
||||
fprintf(stderr,
|
||||
"%s: skipping still unmerged path.\n",
|
||||
path);
|
||||
goto free_return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Grab blobs from given path from HEAD and MERGE_HEAD,
|
||||
* stuff HEAD version in stage #2,
|
||||
* stuff MERGE_HEAD version in stage #3.
|
||||
*/
|
||||
ce_2 = read_one_ent("our", head_sha1, path, namelen, 2);
|
||||
ce_3 = read_one_ent("their", merge_head_sha1, path, namelen, 3);
|
||||
|
||||
if (!ce_2 || !ce_3) {
|
||||
ret = -1;
|
||||
goto free_return;
|
||||
}
|
||||
if (!memcmp(ce_2->sha1, ce_3->sha1, 20) &&
|
||||
ce_2->ce_mode == ce_3->ce_mode) {
|
||||
fprintf(stderr, "%s: identical in both, skipping.\n",
|
||||
path);
|
||||
goto free_return;
|
||||
}
|
||||
|
||||
remove_file_from_cache(path);
|
||||
if (add_cache_entry(ce_2, ADD_CACHE_OK_TO_ADD)) {
|
||||
error("%s: cannot add our version to the index.", path);
|
||||
ret = -1;
|
||||
goto free_return;
|
||||
}
|
||||
if (!add_cache_entry(ce_3, ADD_CACHE_OK_TO_ADD))
|
||||
return 0;
|
||||
error("%s: cannot add their version to the index.", path);
|
||||
ret = -1;
|
||||
free_return:
|
||||
free(ce_2);
|
||||
free(ce_3);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void read_head_pointers(void)
|
||||
{
|
||||
if (read_ref(git_path("HEAD"), head_sha1))
|
||||
die("No HEAD -- no initial commit yet?\n");
|
||||
if (read_ref(git_path("MERGE_HEAD"), merge_head_sha1)) {
|
||||
fprintf(stderr, "Not in the middle of a merge.\n");
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
static int do_unresolve(int ac, const char **av,
|
||||
const char *prefix, int prefix_length)
|
||||
{
|
||||
int i;
|
||||
int err = 0;
|
||||
|
||||
/* Read HEAD and MERGE_HEAD; if MERGE_HEAD does not exist, we
|
||||
* are not doing a merge, so exit with success status.
|
||||
*/
|
||||
read_head_pointers();
|
||||
|
||||
for (i = 1; i < ac; i++) {
|
||||
const char *arg = av[i];
|
||||
const char *p = prefix_path(prefix, prefix_length, arg);
|
||||
err |= unresolve_one(p);
|
||||
if (p != arg)
|
||||
free((char*)p);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
int main(int argc, const char **argv)
|
||||
{
|
||||
|
@ -478,6 +607,7 @@ int main(int argc, const char **argv)
|
|||
int read_from_stdin = 0;
|
||||
const char *prefix = setup_git_directory();
|
||||
int prefix_length = prefix ? strlen(prefix) : 0;
|
||||
char set_executable_bit = 0;
|
||||
|
||||
git_config(git_default_config);
|
||||
|
||||
|
@ -544,8 +674,7 @@ int main(int argc, const char **argv)
|
|||
!strcmp(path, "--chmod=+x")) {
|
||||
if (argc <= i+1)
|
||||
die("git-update-index: %s <path>", path);
|
||||
if (chmod_path(path[8], argv[++i]))
|
||||
die("git-update-index: %s cannot chmod %s", path, argv[i]);
|
||||
set_executable_bit = path[8];
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(path, "--assume-unchanged")) {
|
||||
|
@ -581,6 +710,13 @@ int main(int argc, const char **argv)
|
|||
read_index_info(line_termination);
|
||||
break;
|
||||
}
|
||||
if (!strcmp(path, "--unresolve")) {
|
||||
has_errors = do_unresolve(argc - i, argv + i,
|
||||
prefix, prefix_length);
|
||||
if (has_errors)
|
||||
active_cache_changed = 0;
|
||||
goto finish;
|
||||
}
|
||||
if (!strcmp(path, "--ignore-missing")) {
|
||||
not_new = 1;
|
||||
continue;
|
||||
|
@ -594,12 +730,15 @@ int main(int argc, const char **argv)
|
|||
die("unknown option %s", path);
|
||||
}
|
||||
update_one(path, prefix, prefix_length);
|
||||
if (set_executable_bit)
|
||||
chmod_path(set_executable_bit, path);
|
||||
}
|
||||
if (read_from_stdin) {
|
||||
struct strbuf buf;
|
||||
strbuf_init(&buf);
|
||||
while (1) {
|
||||
char *path_name;
|
||||
const char *p;
|
||||
read_line(&buf, stdin, line_termination);
|
||||
if (buf.eof)
|
||||
break;
|
||||
|
@ -607,11 +746,18 @@ int main(int argc, const char **argv)
|
|||
path_name = unquote_c_style(buf.buf, NULL);
|
||||
else
|
||||
path_name = buf.buf;
|
||||
update_one(path_name, prefix, prefix_length);
|
||||
p = prefix_path(prefix, prefix_length, path_name);
|
||||
update_one(p, NULL, 0);
|
||||
if (set_executable_bit)
|
||||
chmod_path(set_executable_bit, p);
|
||||
if (p != path_name)
|
||||
free((char*) p);
|
||||
if (path_name != buf.buf)
|
||||
free(path_name);
|
||||
}
|
||||
}
|
||||
|
||||
finish:
|
||||
if (active_cache_changed) {
|
||||
if (write_cache(newfd, active_cache, active_nr) ||
|
||||
commit_index_file(&cache_file))
|
||||
|
|
Loading…
Reference in New Issue