Merge GIT v1.5.2-rc3

This commit is contained in:
Johannes Sixt
2007-05-19 23:37:39 +02:00
55 changed files with 5268 additions and 4336 deletions

View File

@@ -35,7 +35,11 @@ Shawn O. Pearce <spearce@spearce.org>
Theodore Ts'o <tytso@mit.edu>
Tony Luck <tony.luck@intel.com>
Uwe Kleine-König <zeisberg@informatik.uni-freiburg.de>
Uwe Kleine-König <Uwe_Zeisberger@digi.com>
Uwe Kleine-König <uzeisberger@io.fsforth.de>
Uwe Kleine-König <ukleinek@informatik.uni-freiburg.de>
Ville Skyttä <scop@xemacs.org>
YOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org>
anonymous <linux@horizon.com>
anonymous <linux@horizon.net>
Dana L. How <how@deathvalley.cswitch.com>

View File

@@ -0,0 +1,30 @@
GIT v1.5.1.4 Release Notes
==========================
Fixes since v1.5.1.3
--------------------
* Bugfixes
- "git-http-fetch" did not work around a bug in libcurl
earlier than 7.16 (curl_multi_remove_handle() was broken).
- "git cvsserver" handles a file that was once removed and
then added again correctly.
- import-tars script (in contrib/) handles GNU tar archives
that contain pathnames longer than 100 bytes (long-link
extension) correctly.
- xdelta test program did not build correctly.
- gitweb sometimes tried incorrectly to apply function to
decode utf8 twice, resulting in corrupt output.
- "git blame -C" mishandled text at the end of a group of
lines.
- "git log/rev-list --boundary" did not produce output
correctly without --left-right option.
- Many documentation updates.

View File

@@ -32,7 +32,7 @@ Updates since v1.5.1
arbitrary filter to contents on check-in/check-out codepath
but this feature is an extremely sharp-edged razor and needs
to be handled with caution (do not use it unless you
understand the earlier mailing list discussion on keyward
understand the earlier mailing list discussion on keyword
expansion).
* The packfile format now optionally suports 64-bit index.
@@ -42,6 +42,13 @@ Updates since v1.5.1
needs more than 32-bit to express offsets of objects in the
pack
* Comes with an updated git-gui 0.7.0
* Updated gitweb:
- can show combined diff for merges;
- uses font size of user's preference, not hardcoded in pixels;
* New commands and options.
- "git bisect start" can optionally take a single bad commit and
@@ -112,6 +119,13 @@ Updates since v1.5.1
- "git blame" uses .mailmap to canonicalize the author name
just like "git shortlog" does.
- "git pack-objects" pays attention to pack.depth
configuration variable.
- "git cherry-pick" and "git revert" does not use .msg file in
the working tree to prepare commit message; instead it uses
$GIT_DIR/MERGE_MSG as other commands.
* Builds
- git-p4import has never been installed; now there is an
@@ -132,11 +146,18 @@ Updates since v1.5.1
- Optimized "git-add $path" in a large directory, most of
whose contents are ignored.
- Optimized "git-diff-tree" for reduced memory footprint.
- The recursive merge strategy updated a worktree file that
was changed identically in two branches, when one of them
renamed it. We do not do that when there is no rename, so
match that behaviour.
- The default pack depth has been increased to 50, as the
recent addition of delta_base_cache makes deeper delta chains
much less expensive to access.
Fixes since v1.5.1
------------------
@@ -165,12 +186,14 @@ this release, unless otherwise noted.
- git-fetch had trouble with a remote with insanely large number
of refs.
- "git clean -d -X" now does not remove non-excluded directories.
* Documentation updates
* Performance Tweaks
--
exec >/var/tmp/1
O=v1.5.2-rc1-32-g125a5f1
O=v1.5.2-rc2-91-g616e40b
echo O=`git describe refs/heads/master`
git shortlog --no-merges $O..refs/heads/master ^refs/heads/maint

View File

@@ -546,6 +546,10 @@ pack.window::
The size of the window used by gitlink:git-pack-objects[1] when no
window size is given on the command line. Defaults to 10.
pack.depth::
The maximum delta depth used by gitlink:git-pack-objects[1] when no
maximum depth is given on the command line. Defaults to 50.
pull.octopus::
The default merge strategy to use when pulling multiple branches
at once.

View File

@@ -7,7 +7,7 @@ git-add - Add file contents to the changeset to be committed next
SYNOPSIS
--------
'git-add' [-n] [-v] [-f] [--interactive | -i] [--] <file>...
'git-add' [-n] [-v] [-f] [--interactive | -i] [-u] [--] <file>...
DESCRIPTION
-----------
@@ -56,6 +56,10 @@ OPTIONS
Add modified contents in the working tree interactively to
the index.
-u::
Update all files that git already knows about. This is what
"git commit -a" does in preparation for making a commit.
\--::
This option can be used to separate command-line options from
the list of files, (useful when filenames might be mistaken

View File

@@ -26,7 +26,7 @@ OPTIONS
Remove untracked directories in addition to untracked files.
-f::
If the git configuration specifies clean.forceRequire as true,
If the git configuration specifies clean.requireForce as true,
git-clean will refuse to run unless given -f or -n.
-n::

View File

@@ -25,7 +25,7 @@ by default.
Supports file additions, removals, and commits that affect binary files.
If the commit is a merge commit, you must tell git-cvsapplycommit what parent
If the commit is a merge commit, you must tell git-cvsexportcommit what parent
should the changeset be done against.
OPTIONS

View File

@@ -548,7 +548,6 @@ lightweight (non-annotated) tags see the `reset` command below.
'from' SP <committish> LF
'tagger' SP <name> SP LT <email> GT SP <when> LF
data
LF
....
where `<name>` is the name of the tag to create.

View File

@@ -51,6 +51,9 @@ include::pretty-formats.txt[]
a record about how the tip of a reference was changed.
See also gitlink:git-reflog[1].
--decorate::
Print out the ref names of any commits that are shown.
<paths>...::
Show only commits that affect the specified paths.

View File

@@ -42,8 +42,8 @@ OPTIONS
Show other files in the output
-i|--ignored::
Show ignored files in the output
Note the this also reverses any exclude list present.
Show ignored files in the output.
Note that this also reverses any exclude list present.
-s|--stage::
Show stage files in the output

View File

@@ -83,7 +83,7 @@ base-name::
it too deep affects the performance on the unpacker
side, because delta data needs to be applied that many
times to get to the necessary object.
The default value for both --window and --depth is 10.
The default value for --window is 10 and --depth is 50.
--incremental::
This flag causes an object already in a pack ignored

View File

@@ -63,7 +63,7 @@ OPTIONS
space. `--depth` limits the maximum delta depth; making it too deep
affects the performance on the unpacker side, because delta data needs
to be applied that many times to get to the necessary object.
The default value for both --window and --depth is 10.
The default value for --window is 10 and --depth is 50.
Configuration

View File

@@ -255,7 +255,7 @@ reachable from `r1` from the set of commits reachable from
A similar notation "`r1\...r2`" is called symmetric difference
of `r1` and `r2` and is defined as
"`r1 r2 --not $(git-merge-base --all r1 r2)`".
It it the set of commits that are reachable from either one of
It is the set of commits that are reachable from either one of
`r1` or `r2` but not from both.
Two other shorthands for naming a set that is formed by a commit

View File

@@ -27,6 +27,9 @@ Modifies the index or directory cache. Each file mentioned is updated
into the index and any 'unmerged' or 'needs updating' state is
cleared.
See also gitlink:git-add[1] for a more user-friendly way to do some of
the most common operations on the index.
The way "git-update-index" handles files it is told about can be modified
using the various options:
@@ -306,7 +309,8 @@ The command looks at `core.ignorestat` configuration variable. See
See Also
--------
gitlink:git-config[1]
gitlink:git-config[1],
gitlink:git-add[1]
Author

View File

@@ -21,10 +21,9 @@ and full access to internals.
See this link:tutorial.html[tutorial] to get started, then see
link:everyday.html[Everyday Git] for a useful minimum set of commands, and
"man git-commandname" for documentation of each command. CVS users may
also want to read link:cvs-migration.html[CVS migration].
link:user-manual.html[Git User's Manual] is still work in
progress, but when finished hopefully it will guide a new user
in a coherent way to git enlightenment ;-).
also want to read link:cvs-migration.html[CVS migration]. See
link:user-manual.html[Git User's Manual] for a more in-depth
introduction.
The COMMAND is either a name of a Git command (see below) or an alias
as defined in the configuration file (see gitlink:git-config[1]).
@@ -41,7 +40,11 @@ Documentation for older releases are available here:
* link:RelNotes-1.5.1.txt[release notes for 1.5.1]
* link:v1.5.1.2/git.html[documentation for release 1.5.1.2]
* link:v1.5.1.4/git.html[documentation for release 1.5.1.4]
* link:RelNotes-1.5.1.4.txt[release notes for 1.5.1.4]
* link:RelNotes-1.5.1.3.txt[release notes for 1.5.1.3]
* link:RelNotes-1.5.1.2.txt[release notes for 1.5.1.2]

View File

@@ -23,12 +23,14 @@ pages. For a command such as "git clone", just use
$ man git-clone
------------------------------------------------
[[git-quick-start]]
Git Quick Start
===============
This is a quick summary of the major commands; the following chapters
will explain how these work in more detail.
[[quick-creating-a-new-repository]]
Creating a new repository
-------------------------
@@ -50,11 +52,12 @@ $ git clone git://example.com/pub/project.git
$ cd project
-----------------------------------------------
[[managing-branches]]
Managing branches
-----------------
-----------------------------------------------
$ git branch # list all branches in this repo
$ git branch # list all local branches in this repo
$ git checkout test # switch working directory to branch "test"
$ git branch new # create branch "new" starting at current HEAD
$ git branch -d new # delete branch "new"
@@ -112,6 +115,7 @@ $ git branch -r # list all remote branches
-----------------------------------------------
[[exploring-history]]
Exploring history
-----------------
@@ -147,13 +151,14 @@ $ git bisect bad # if this revision is bad.
# repeat until done.
-----------------------------------------------
[[making-changes]]
Making changes
--------------
Make sure git knows who to blame:
------------------------------------------------
$ cat >~/.gitconfig <<\EOF
$ cat >>~/.gitconfig <<\EOF
[user]
name = Your Name Comes Here
email = you@yourdomain.example.com
@@ -177,6 +182,7 @@ $ git commit d.txt # use latest content only of d.txt
$ git commit -a # use latest content of all tracked files
-----------------------------------------------
[[merging]]
Merging
-------
@@ -187,6 +193,7 @@ $ git pull git://example.com/project.git master
$ git pull . test # equivalent to git merge test
-----------------------------------------------
[[sharing-your-changes]]
Sharing your changes
--------------------
@@ -232,6 +239,7 @@ $ git remote add example ssh://example.com/project.git
$ git push example test
-----------------------------------------------
[[repository-maintenance]]
Repository maintenance
----------------------
@@ -247,9 +255,11 @@ Recompress, remove unused cruft:
$ git gc
-----------------------------------------------
[[repositories-and-branches]]
Repositories and Branches
=========================
[[how-to-get-a-git-repository]]
How to get a git repository
---------------------------
@@ -280,6 +290,7 @@ contains all the information about the history of the project.
In most of the following, examples will be taken from one of the two
repositories above.
[[how-to-check-out]]
How to check out a different version of a project
-------------------------------------------------
@@ -352,6 +363,7 @@ particular point in history, then resetting that branch may leave you
with no way to find the history it used to point to; so use this command
carefully.
[[understanding-commits]]
Understanding History: Commits
------------------------------
@@ -407,6 +419,7 @@ In fact, in <<git-internals>> we shall see that everything stored in git
history, including file data and directory contents, is stored in an object
with a name that is a hash of its contents.
[[understanding-reachability]]
Understanding history: commits, parents, and reachability
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -431,6 +444,7 @@ if commit X is an ancestor of commit Y. Equivalently, you could say
that Y is a descendent of X, or that there is a chain of parents
leading from commit Y to commit X.
[[history-diagrams]]
Understanding history: History diagrams
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -450,6 +464,7 @@ lines drawn with - / and \. Time goes left to right:
If we need to talk about a particular commit, the character "o" may
be replaced with another letter or number.
[[what-is-a-branch]]
Understanding history: What is a branch?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -463,6 +478,7 @@ the line of three commits leading up to that point as all being part of
However, when no confusion will result, we often just use the term
"branch" both for branches and for branch heads.
[[manipulating-branches]]
Manipulating branches
---------------------
@@ -480,8 +496,8 @@ git branch <branch> <start-point>::
including using a branch name or a tag name
git branch -d <branch>::
delete the branch <branch>; if the branch you are deleting
points to a commit which is not reachable from this branch,
this command will fail with a warning.
points to a commit which is not reachable from the current
branch, this command will fail with a warning.
git branch -D <branch>::
even if the branch points to a commit not reachable
from the current branch, you may know that that commit
@@ -527,18 +543,18 @@ and git branch shows that you are no longer on a branch:
------------------------------------------------
$ cat .git/HEAD
427abfa28afedffadfca9dd8b067eb6d36bac53f
git branch
$ git branch
* (no branch)
master
------------------------------------------------
In this case we say that the HEAD is "detached".
This can be an easy way to check out a particular version without having
to make up a name for a new branch. However, keep in mind that when you
switch away from the (for example, by checking out something else), you
can lose track of what the HEAD used to point to.
This is an easy way to check out a particular version without having to
make up a name for the new branch. You can still create a new branch
(or tag) for this version later if you decide to.
[[examining-remote-branches]]
Examining branches from a remote repository
-------------------------------------------
@@ -586,13 +602,9 @@ shorthand:
The full name is occasionally useful if, for example, there ever
exists a tag and a branch with the same name.
As another useful shortcut, if the repository "origin" posesses only
a single branch, you can refer to that branch as just "origin".
More generally, if you have defined a remote repository named
"example", you can refer to the branch in that repository as
"example". And for a repository with multiple branches, this will
refer to the branch designated as the "HEAD" branch.
As another useful shortcut, the "HEAD" of a repository can be referred
to just using the name of that repository. So, for example, "origin"
is usually a shortcut for the HEAD branch in the repository "origin".
For the complete list of paths which git checks for references, and
the order it uses to decide which to choose when there are multiple
@@ -612,6 +624,7 @@ remote-tracking branches to the latest version found in her
repository. It will not touch any of your own branches--not even the
"master" branch that was created for you on clone.
[[fetching-branches]]
Fetching branches from other repositories
-----------------------------------------
@@ -654,6 +667,7 @@ or delete these configuration options by editing .git/config with a
text editor. (See the "CONFIGURATION FILE" section of
gitlink:git-config[1] for details.)
[[exploring-git-history]]
Exploring git history
=====================
@@ -668,6 +682,7 @@ history of a project.
We start with one specialized tool that is useful for finding the
commit that introduced a bug into a project.
[[using-bisect]]
How to use bisect to find a regression
--------------------------------------
@@ -735,6 +750,7 @@ $ git reset --hard fb47ddb2db...
then test, run "bisect good" or "bisect bad" as appropriate, and
continue.
[[naming-commits]]
Naming commits
--------------
@@ -799,6 +815,7 @@ $ git rev-parse origin
e05db0fd4f31dde7005f075a84f96b360d05984b
-------------------------------------------------
[[creating-tags]]
Creating tags
-------------
@@ -811,11 +828,12 @@ $ git tag stable-1 1b2e1d63ff
You can use stable-1 to refer to the commit 1b2e1d63ff.
This creates a "lightweight" tag. If the tag is a tag you wish to
share with others, and possibly sign cryptographically, then you
should create a tag object instead; see the gitlink:git-tag[1] man
page for details.
This creates a "lightweight" tag. If you would also like to include a
comment with the tag, and possibly sign it cryptographically, then you
should create a tag object instead; see the gitlink:git-tag[1] man page
for details.
[[browsing-revisions]]
Browsing revisions
------------------
@@ -857,6 +875,7 @@ backwards through the parents; however, since git history can contain
multiple independent lines of development, the particular order that
commits are listed in may be somewhat arbitrary.
[[generating-diffs]]
Generating diffs
----------------
@@ -878,6 +897,7 @@ but not from master. Note that if master also has commits which are
not reachable from test, then the combined result of these patches
will not be the same as the diff produced by the git-diff example.
[[viewing-old-file-versions]]
Viewing old file versions
-------------------------
@@ -893,9 +913,11 @@ $ git show v2.5:fs/locks.c
Before the colon may be anything that names a commit, and after it
may be any path to a file tracked by git.
[[history-examples]]
Examples
--------
[[checking-for-equal-branches]]
Check whether two branches point at the same history
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -928,6 +950,7 @@ $ git log origin...master
will return no commits when the two branches are equal.
[[finding-tagged-descendants]]
Find first tagged version including a given fix
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -1013,27 +1036,29 @@ Which shows that e05db0fd is reachable from itself, from v1.5.0-rc1, and
from v1.5.0-rc2, but not from v1.5.0-rc0.
[[Developing-with-git]]
Developing with git
===================
[[telling-git-your-name]]
Telling git your name
---------------------
Before creating any commits, you should introduce yourself to git. The
easiest way to do so is:
easiest way to do so is to make sure the following lines appear in a
file named .gitconfig in your home directory:
------------------------------------------------
$ cat >~/.gitconfig <<\EOF
[user]
name = Your Name Comes Here
email = you@yourdomain.example.com
EOF
------------------------------------------------
(See the "CONFIGURATION FILE" section of gitlink:git-config[1] for
details on the configuration file.)
[[creating-a-new-repository]]
Creating a new repository
-------------------------
@@ -1147,9 +1172,12 @@ $ git diff --cached # difference between HEAD and the index; what
$ git diff # difference between the index file and your
# working directory; changes that would not
# be included if you ran "commit" now.
$ git diff HEAD # difference between HEAD and working tree; what
# would be committed if you ran "commit -a" now.
$ git status # a brief per-file summary of the above.
-------------------------------------------------
[[creating-good-commit-messages]]
Creating good commit messages
-----------------------------
@@ -1160,6 +1188,7 @@ description. Tools that turn commits into email, for example, use
the first line on the Subject line and the rest of the commit in the
body.
[[how-to-merge]]
How to merge
------------
@@ -1192,8 +1221,6 @@ If you examine the resulting commit using gitk, you will see that it
has two parents, one pointing to the top of the current branch, and
one to the top of the other branch.
In more detail:
[[resolving-a-merge]]
Resolving a merge
-----------------
@@ -1237,6 +1264,7 @@ your own if desired.
The above is all you need to know to resolve a simple merge. But git
also provides more information to help resolve conflicts:
[[conflict-resolution]]
Getting conflict-resolution help during a merge
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -1329,6 +1357,9 @@ $ gitk --merge
These will display all commits which exist only on HEAD or on
MERGE_HEAD, and which touch an unmerged file.
You may also use gitlink:git-mergetool, which lets you merge the
unmerged files using external tools such as emacs or kdiff3.
Each time you resolve the conflicts in a file and update the index:
-------------------------------------------------
@@ -1360,6 +1391,7 @@ throw away a commit you have already committed if that commit may
itself have been merged into another branch, as doing so may confuse
further merges.
[[fast-forwards]]
Fast-forward merges
-------------------
@@ -1368,13 +1400,13 @@ differently. Normally, a merge results in a merge commit, with two
parents, one pointing at each of the two lines of development that
were merged.
However, if one of the two lines of development is completely
contained within the other--so every commit present in the one is
already contained in the other--then git just performs a
<<fast-forwards,fast forward>>; the head of the current branch is
moved forward to point at the head of the merged-in branch, without
any new commits being created.
However, if the current branch is a descendant of the other--so every
commit present in the one is already contained in the other--then git
just performs a "fast forward"; the head of the current branch is moved
forward to point at the head of the merged-in branch, without any new
commits being created.
[[fixing-mistakes]]
Fixing mistakes
---------------
@@ -1399,6 +1431,7 @@ fundamentally different ways to fix the problem:
change, and cannot correctly perform repeated merges from
a branch that has had its history changed.
[[reverting-a-commit]]
Fixing a mistake with a new commit
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -1453,6 +1486,7 @@ It is also possible to edit commits further back in the history, but
this is an advanced topic to be left for
<<cleaning-up-history,another chapter>>.
[[checkout-of-path]]
Checking out an old version of a file
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -1479,6 +1513,7 @@ $ git show HEAD^:path/to/file
which will display the given version of the file.
[[ensuring-good-performance]]
Ensuring good performance
-------------------------
@@ -1495,9 +1530,12 @@ $ git gc
to recompress the archive. This can be very time-consuming, so
you may prefer to run git-gc when you are not doing other work.
[[ensuring-reliability]]
Ensuring reliability
--------------------
[[checking-for-corruption]]
Checking the repository for corruption
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -1518,8 +1556,10 @@ dangling tree b24c2473f1fd3d91352a624795be026d64c8841f
...
-------------------------------------------------
Dangling objects are objects that are harmless, but also unnecessary;
you can remove them at any time with gitlink:git-prune[1] or the --prune
Dangling objects are not a problem. At worst they may take up a little
extra disk space. They can sometimes provide a last-resort method of
recovery lost work--see <<dangling-objects>> for details. However, if
you want, you may remove them with gitlink:git-prune[1] or the --prune
option to gitlink:git-gc[1]:
-------------------------------------------------
@@ -1530,12 +1570,11 @@ This may be time-consuming. Unlike most other git operations (including
git-gc when run without any options), it is not safe to prune while
other git operations are in progress in the same repository.
For more about dangling objects, see <<dangling-objects>>.
[[recovering-lost-changes]]
Recovering lost changes
~~~~~~~~~~~~~~~~~~~~~~~
[[reflogs]]
Reflogs
^^^^^^^
@@ -1560,8 +1599,19 @@ $ git show master@{2} # See where the branch pointed 2,
$ git show master@{3} # 3, ... changes ago.
$ gitk master@{yesterday} # See where it pointed yesterday,
$ gitk master@{"1 week ago"} # ... or last week
$ git log --walk-reflogs master # show reflog entries for master
-------------------------------------------------
A separate reflog is kept for the HEAD, so
-------------------------------------------------
$ git show HEAD@{"1 week ago"}
-------------------------------------------------
will show what HEAD pointed to one week ago, not what the current branch
pointed to one week ago. This allows you to see the history of what
you've checked out.
The reflogs are kept by default for 30 days, after which they may be
pruned. See gitlink:git-reflog[1] and gitlink:git-gc[1] to learn
how to control this pruning, and see the "SPECIFYING REVISIONS"
@@ -1572,15 +1622,16 @@ While normal history is shared by every repository that works on the
same project, the reflog history is not shared: it tells you only about
how the branches in your local repository have changed over time.
[[dangling-object-recovery]]
Examining dangling objects
^^^^^^^^^^^^^^^^^^^^^^^^^^
In some situations the reflog may not be able to save you. For
example, suppose you delete a branch, then realize you need the history
it contained. The reflog is also deleted; however, if you have not
yet pruned the repository, then you may still be able to find
the lost commits; run git-fsck and watch for output that mentions
"dangling commits":
In some situations the reflog may not be able to save you. For example,
suppose you delete a branch, then realize you need the history it
contained. The reflog is also deleted; however, if you have not yet
pruned the repository, then you may still be able to find the lost
commits in the dangling objects that git-fsck reports. See
<<dangling-objects>> for the details.
-------------------------------------------------
$ git fsck
@@ -1612,7 +1663,11 @@ reference pointing to it, for example, a new branch:
$ git branch recovered-branch 7281251ddd
------------------------------------------------
Other types of dangling objects (blobs and trees) are also possible, and
dangling objects can arise in other situations.
[[sharing-development]]
Sharing development with others
===============================
@@ -1649,9 +1704,16 @@ so often you can accomplish the above with just
$ git pull
-------------------------------------------------
See the descriptions of the branch.<name>.remote and
branch.<name>.merge options in gitlink:git-config[1] to learn
how to control these defaults depending on the current branch.
See the descriptions of the branch.<name>.remote and branch.<name>.merge
options in gitlink:git-config[1] to learn how to control these defaults
depending on the current branch. Also note that the --track option to
gitlink:git-branch[1] and gitlink:git-checkout[1] can be used to
automatically set the default remote branch to pull from at the time
that a branch is created:
-------------------------------------------------
$ git checkout --track -b origin/maint maint
-------------------------------------------------
In addition to saving you keystrokes, "git pull" also helps you by
producing a default commit message documenting the branch and
@@ -1672,6 +1734,7 @@ $ git merge branch
are roughly equivalent. The former is actually very commonly used.
[[submitting-patches]]
Submitting patches to a project
-------------------------------
@@ -1693,6 +1756,7 @@ use the gitlink:git-send-email[1] script to automate the process.
Consult the mailing list for your project first to determine how they
prefer such patches be handled.
[[importing-patches]]
Importing patches to a project
------------------------------
@@ -1772,14 +1836,14 @@ Now, assume your personal repository is in the directory ~/proj. We
first create a new clone of the repository:
-------------------------------------------------
$ git clone --bare proj-clone.git
$ git clone --bare ~/proj proj.git
-------------------------------------------------
The resulting directory proj-clone.git will contains a "bare" git
repository--it is just the contents of the ".git" directory, without
a checked-out copy of a working directory.
The resulting directory proj.git contains a "bare" git repository--it is
just the contents of the ".git" directory, without a checked-out copy of
a working directory.
Next, copy proj-clone.git to the server where you plan to host the
Next, copy proj.git to the server where you plan to host the
public repository. You can use scp, rsync, or whatever is most
convenient.
@@ -1805,7 +1869,7 @@ adjustments to give web clients some extra information they need:
-------------------------------------------------
$ mv proj.git /home/you/public_html/proj.git
$ cd proj.git
$ git update-server-info
$ git --bare update-server-info
$ chmod a+x hooks/post-update
-------------------------------------------------
@@ -1872,7 +1936,7 @@ As with git-fetch, you may also set up configuration options to
save typing; so, for example, after
-------------------------------------------------
$ cat >.git/config <<EOF
$ cat >>.git/config <<EOF
[remote "public-repo"]
url = ssh://yourserver.com/~you/proj.git
EOF
@@ -1888,6 +1952,7 @@ See the explanations of the remote.<name>.url, branch.<name>.remote,
and remote.<name>.push options in gitlink:git-config[1] for
details.
[[setting-up-a-shared-repository]]
Setting up a shared repository
------------------------------
@@ -1897,6 +1962,7 @@ all push to and pull from a single shared repository. See
link:cvs-migration.txt[git for CVS users] for instructions on how to
set this up.
[[setting-up-gitweb]]
Allow web browsing of a repository
----------------------------------
@@ -1904,6 +1970,7 @@ The gitweb cgi script provides users an easy way to browse your
project's files and history without having to install git; see the file
gitweb/INSTALL in the git source tree for instructions on setting it up.
[[sharing-development-examples]]
Examples
--------
@@ -1921,6 +1988,7 @@ cause git's merge machinery (for example) to do the wrong thing.
However, there is a situation in which it can be useful to violate this
assumption.
[[patch-series]]
Creating the perfect patch series
---------------------------------
@@ -1953,6 +2021,7 @@ We will introduce some tools that can help you do this, explain how to
use them, and then explain some of the problems that can arise because
you are rewriting history.
[[using-git-rebase]]
Keeping a patch series up to date using git-rebase
--------------------------------------------------
@@ -2034,6 +2103,7 @@ return mywork to the state it had before you started the rebase:
$ git rebase --abort
-------------------------------------------------
[[modifying-one-commit]]
Modifying a single commit
-------------------------
@@ -2079,6 +2149,7 @@ Note that the immutable nature of git history means that you haven't really
"modified" existing commits; instead, you have replaced the old commits with
new commits having new object names.
[[reordering-patch-series]]
Reordering or selecting from a patch series
-------------------------------------------
@@ -2108,6 +2179,7 @@ $ git reset --hard origin
Then modify, reorder, or eliminate patches as preferred before applying
them again with gitlink:git-am[1].
[[patch-series-tools]]
Other tools
-----------
@@ -2115,6 +2187,7 @@ There are numerous other tools, such as stgit, which exist for the
purpose of maintaining a patch series. These are outside of the scope of
this manual.
[[problems-with-rewriting-history]]
Problems with rewriting history
-------------------------------
@@ -2163,9 +2236,11 @@ branches into their own work.
For true distributed development that supports proper merging,
published branches should never be rewritten.
[[advanced-branch-management]]
Advanced branch management
==========================
[[fetching-individual-branches]]
Fetching individual branches
----------------------------
@@ -2191,18 +2266,18 @@ $ git fetch git://example.com/proj.git master:example-master
will create a new branch named "example-master" and store in it the
branch named "master" from the repository at the given URL. If you
already have a branch named example-master, it will attempt to
"fast-forward" to the commit given by example.com's master branch. So
next we explain what a fast-forward is:
<<fast-forwards,fast-forward>> to the commit given by example.com's
master branch. In more detail:
[[fast-forwards]]
Understanding git history: fast-forwards
----------------------------------------
[[fetch-fast-forwards]]
git fetch and fast-forwards
---------------------------
In the previous example, when updating an existing branch, "git
fetch" checks to make sure that the most recent commit on the remote
branch is a descendant of the most recent commit on your copy of the
branch before updating your copy of the branch to point at the new
commit. Git calls this process a "fast forward".
commit. Git calls this process a <<fast-forwards,fast forward>>.
A fast forward looks something like this:
@@ -2232,6 +2307,7 @@ situation above this may mean losing the commits labeled "a" and "b",
unless you've already created a reference of your own pointing to
them.
[[forcing-fetch]]
Forcing git fetch to do non-fast-forward updates
------------------------------------------------
@@ -2242,10 +2318,17 @@ descendant of the old head, you may force the update with:
$ git fetch git://example.com/proj.git +master:refs/remotes/example/master
-------------------------------------------------
Note the addition of the "+" sign. Be aware that commits that the
old version of example/master pointed at may be lost, as we saw in
the previous section.
Note the addition of the "+" sign. Alternatively, you can use the "-f"
flag to force updates of all the fetched branches, as in:
-------------------------------------------------
$ git fetch -f origin
-------------------------------------------------
Be aware that commits that the old version of example/master pointed at
may be lost, as we saw in the previous section.
[[remote-branch-configuration]]
Configuring remote branches
---------------------------
@@ -2289,9 +2372,8 @@ $ git config remote.example.fetch master:refs/remotes/example/master
then the following commands will all do the same thing:
-------------------------------------------------
$ git fetch git://example.com/proj.git master:ref/remotes/example/master
$ git fetch example master:ref/remotes/example/master
$ git fetch example example/master
$ git fetch git://example.com/proj.git master:refs/remotes/example/master
$ git fetch example master:refs/remotes/example/master
$ git fetch example
-------------------------------------------------
@@ -2319,6 +2401,7 @@ Git internals
Git depends on two fundamental abstractions: the "object database", and
the "current directory cache" aka "index".
[[the-object-database]]
The Object Database
-------------------
@@ -2328,7 +2411,7 @@ approximated by the SHA1 hash of the object itself. Objects may refer
to other objects (by referencing their SHA1 hash), and so you can
build up a hierarchy of objects.
All objects have a statically determined "type" aka "tag", which is
All objects have a statically determined "type" which is
determined at object creation time, and which identifies the format of
the object (i.e. how it is used, and how it can refer to other
objects). There are currently four different object types: "blob",
@@ -2351,7 +2434,7 @@ the time of the commit). In addition, a "commit" refers to one or more
that directory hierarchy.
As a special case, a commit object with no parents is called the "root"
object, and is the point of an initial project commit. Each project
commit, and is the point of an initial project commit. Each project
must have at least one root, and while you can tie several different
root objects together into one project by creating a commit object which
has two or more separate roots as its ultimate parents, that's probably
@@ -2388,6 +2471,7 @@ to just verifying their superficial consistency through the hash).
The object types in some more detail:
[[blob-object]]
Blob Object
-----------
@@ -2409,6 +2493,7 @@ file is associated with in any way.
A blob is typically created when gitlink:git-update-index[1]
is run, and its data can be accessed by gitlink:git-cat-file[1].
[[tree-object]]
Tree Object
-----------
@@ -2450,6 +2535,7 @@ A tree is created with gitlink:git-write-tree[1] and
its data can be accessed by gitlink:git-ls-tree[1].
Two trees can be compared with gitlink:git-diff-tree[1].
[[commit-object]]
Commit Object
-------------
@@ -2467,7 +2553,7 @@ that the tree is "good" or that the merge information makes sense.
The parents do not have to actually have any relationship with the
result, for example.
Note on commits: unlike real SCM's, commits do not contain
Note on commits: unlike some SCM's, commits do not contain
rename information or file mode change information. All of that is
implicit in the trees involved (the result tree, and the result trees
of the parents), and describing that makes no sense in this idiotic
@@ -2476,6 +2562,7 @@ file manager.
A commit is created with gitlink:git-commit-tree[1] and
its data can be accessed by gitlink:git-cat-file[1].
[[trust]]
Trust
-----
@@ -2505,6 +2592,7 @@ like GPG/PGP.
To assist in this, git also provides the tag object...
[[tag-object]]
Tag Object
----------
@@ -2527,11 +2615,12 @@ and the signature can be verified by
gitlink:git-verify-tag[1].
[[the-index]]
The "index" aka "Current Directory Cache"
-----------------------------------------
The index is a simple binary file, which contains an efficient
representation of a virtual directory content at some random time. It
representation of the contents of a virtual directory. It
does so by a simple array that associates a set of names, dates,
permissions and content (aka "blob") objects together. The cache is
always kept ordered by name, and names are unique (with a few very
@@ -2581,6 +2670,7 @@ been written back to the backing store.
[[the-workflow]]
The Workflow
------------
@@ -2590,6 +2680,7 @@ index), but most operations move data to and from the index file. Either
from the database or from the working directory. Thus there are four
main combinations:
[[working-directory-to-index]]
working directory -> index
~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -2623,6 +2714,7 @@ stat information. It will 'not' update the object status itself, and
it will only update the fields that are used to quickly test whether
an object still matches its old backing store object.
[[index-to-object-database]]
index -> object database
~~~~~~~~~~~~~~~~~~~~~~~~
@@ -2638,6 +2730,7 @@ and it will return the name of the resulting top-level tree. You can
use that tree to re-generate the index at any time by going in the
other direction:
[[object-database-to-index]]
object database -> index
~~~~~~~~~~~~~~~~~~~~~~~~
@@ -2654,6 +2747,7 @@ and your index file will now be equivalent to the tree that you saved
earlier. However, that is only your 'index' file: your working
directory contents have not been modified.
[[index-to-working-directory]]
index -> working directory
~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -2683,6 +2777,7 @@ need to use the "-f" flag ('before' the "-a" flag or the filename) to
Finally, there are a few odds and ends which are not purely moving
from one representation to the other:
[[tying-it-all-together]]
Tying it all together
~~~~~~~~~~~~~~~~~~~~~
@@ -2758,6 +2853,7 @@ various pieces fit together.
------------
[[examining-the-data]]
Examining the data
------------------
@@ -2793,6 +2889,7 @@ $ git-cat-file commit HEAD
to see what the top commit was.
[[merging-multiple-trees]]
Merging multiple trees
----------------------
@@ -2826,7 +2923,7 @@ since the tree object information is always the first line in a commit
object.
Once you know the three trees you are going to merge (the one "original"
tree, aka the common case, and the two "result" trees, aka the branches
tree, aka the common tree, and the two "result" trees, aka the branches
you want to merge), you do a "merge" read into the index. This will
complain if it has to throw away your old index contents, so you should
make sure that you've committed those - in fact you would normally
@@ -2844,6 +2941,7 @@ index file, and you can just write the result out with
`git-write-tree`.
[[merging-multiple-trees-2]]
Merging multiple trees, continued
---------------------------------
@@ -2879,14 +2977,14 @@ obviously the final outcome is what is in `HEAD`. What the
above example shows is that file `hello.c` was changed from
`$orig` to `HEAD` and `$orig` to `$target` in a different way.
You could resolve this by running your favorite 3-way merge
program, e.g. `diff3` or `merge`, on the blob objects from
these three stages yourself, like this:
program, e.g. `diff3`, `merge`, or git's own merge-file, on
the blob objects from these three stages yourself, like this:
------------------------------------------------
$ git-cat-file blob 263414f... >hello.c~1
$ git-cat-file blob 06fa6a2... >hello.c~2
$ git-cat-file blob cc44c73... >hello.c~3
$ merge hello.c~2 hello.c~1 hello.c~3
$ git merge-file hello.c~2 hello.c~1 hello.c~3
------------------------------------------------
This would leave the merge result in `hello.c~2` file, along
@@ -2914,6 +3012,7 @@ $ git-merge-index git-merge-one-file hello.c
and that is what higher level `git merge -s resolve` is implemented with.
[[pack-files]]
How git stores objects efficiently: pack files
----------------------------------------------
@@ -2984,11 +3083,10 @@ objects. They are not a problem.
The most common cause of dangling objects is that you've rebased a
branch, or you have pulled from somebody else who rebased a branch--see
<<cleaning-up-history>>. In that case, the old head of the original
branch still exists, as does obviously everything it pointed to. The
branch pointer itself just doesn't, since you replaced it with another
one.
branch still exists, as does everything it pointed to. The branch
pointer itself just doesn't, since you replaced it with another one.
There are also other situations too that cause dangling objects. For
There are also other situations that cause dangling objects. For
example, a "dangling blob" may arise because you did a "git add" of a
file, but then, before you actually committed it and made it part of the
bigger picture, you changed something else in that file and committed
@@ -3010,15 +3108,22 @@ be how you recover your old tree (say, you did a rebase, and realized
that you really didn't want to - you can look at what dangling objects
you have, and decide to reset your head to some old dangling state).
For commits, the most useful thing to do with dangling objects tends to
be to do a simple
For commits, you can just use:
------------------------------------------------
$ gitk <dangling-commit-sha-goes-here> --not --all
------------------------------------------------
For blobs and trees, you can't do the same, but you can examine them.
You can just do
This asks for all the history reachable from the given commit but not
from any branch, tag, or other reference. If you decide it's something
you want, you can always create a new reference to it, e.g.,
------------------------------------------------
$ git branch recovered-branch <dangling-commit-sha-goes-here>
------------------------------------------------
For blobs and trees, you can't do the same, but you can still examine
them. You can just do
------------------------------------------------
$ git show <dangling-blob/tree-sha-goes-here>
@@ -3055,8 +3160,10 @@ confusing and scary messages, but it won't actually do anything bad. In
contrast, running "git prune" while somebody is actively changing the
repository is a *BAD* idea).
[[glossary]]
include::glossary.txt[]
[[todo]]
Notes and todo list for this manual
===================================

View File

@@ -1,7 +1,7 @@
#!/bin/sh
GVF=GIT-VERSION-FILE
DEF_VER=v1.5.2-rc2.GIT
DEF_VER=v1.5.2-rc3.GIT
LF='
'

View File

@@ -114,7 +114,11 @@ all:
#
# Define NO_TCLTK if you do not want Tcl/Tk GUI.
#
# The TCLTK_PATH variable governs the location of the Tck/Tk interpreter.
# The TCL_PATH variable governs the location of the Tcl interpreter
# used to optimize git-gui for your system. Only used if NO_TCLTK
# is not set. Defaults to the bare 'tclsh'.
#
# The TCLTK_PATH variable governs the location of the Tcl/Tk interpreter.
# If not set it defaults to the bare 'wish'. If it is set to the empty
# string then NO_TCLTK will be forced (this is used by configure script).
#
@@ -140,7 +144,8 @@ STRIP ?= strip
prefix = $(HOME)
bindir = $(prefix)/bin
gitexecdir = $(bindir)
template_dir = $(prefix)/share/git-core/templates/
sharedir = $(prefix)/share/
template_dir = $(sharedir)/git-core/templates/
ifeq ($(prefix),/usr)
sysconfdir = /etc
else
@@ -165,15 +170,18 @@ GITWEB_FAVICON = git-favicon.png
GITWEB_SITE_HEADER =
GITWEB_SITE_FOOTER =
export prefix bindir gitexecdir template_dir sysconfdir
export prefix bindir gitexecdir sharedir template_dir sysconfdir
CC = gcc
AR = ar
TAR = tar
INSTALL = install
RPMBUILD = rpmbuild
TCL_PATH = tclsh
TCLTK_PATH = wish
export TCL_PATH TCLTK_PATH
# sparse is architecture-neutral, which means that we need to tell it
# explicitly what architecture to check for. Fix this up for yours..
SPARSE_FLAGS = -D__BIG_ENDIAN__ -D__powerpc__
@@ -729,7 +737,7 @@ LIB_OBJS += $(COMPAT_OBJS)
ALL_CFLAGS += $(BASIC_CFLAGS)
ALL_LDFLAGS += $(BASIC_LDFLAGS)
export prefix gitexecdir TAR INSTALL DESTDIR SHELL_PATH template_dir
export TAR INSTALL DESTDIR SHELL_PATH
### Build rules
@@ -738,7 +746,7 @@ all: $(ALL_PROGRAMS) $(BUILT_INS) $(OTHER_PROGRAMS)
all:
ifndef NO_TCLTK
$(QUIET_SUBDIR0)git-gui $(QUIET_SUBDIR1) TCLTK_PATH='$(TCLTK_PATH_SQ)' all
$(QUIET_SUBDIR0)git-gui $(QUIET_SUBDIR1) all
endif
$(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' all
$(QUIET_SUBDIR0)templates $(QUIET_SUBDIR1) NOEXECTEMPL='$(NOEXECTEMPL)'

View File

@@ -64,6 +64,7 @@ static char tmpname[PATH_MAX];
static unsigned char pack_file_sha1[20];
static int progress = 1;
static int window = 10;
static int depth = 50;
static int pack_to_stdout;
static int num_preferred_base;
static struct progress progress_state;
@@ -559,6 +560,12 @@ static off_t write_one(struct sha1file *f,
return offset + size;
}
static int open_object_dir_tmp(const char *path)
{
snprintf(tmpname, sizeof(tmpname), "%s/%s", get_object_directory(), path);
return mkstemp(tmpname);
}
static off_t write_pack_file(void)
{
uint32_t i;
@@ -571,9 +578,7 @@ static off_t write_pack_file(void)
f = sha1fd(1, "<stdout>");
do_progress >>= 1;
} else {
int fd;
snprintf(tmpname, sizeof(tmpname), "tmp_pack_XXXXXX");
fd = mkstemp(tmpname);
int fd = open_object_dir_tmp("tmp_pack_XXXXXX");
if (fd < 0)
die("unable to create %s: %s\n", tmpname, strerror(errno));
pack_tmp_name = xstrdup(tmpname);
@@ -623,10 +628,8 @@ static void write_index_file(off_t last_obj_offset, unsigned char *sha1)
uint32_t array[256];
uint32_t i, index_version;
SHA_CTX ctx;
int fd;
snprintf(tmpname, sizeof(tmpname), "tmp_idx_XXXXXX");
fd = mkstemp(tmpname);
int fd = open_object_dir_tmp("tmp_idx_XXXXXX");
if (fd < 0)
die("unable to create %s: %s\n", tmpname, strerror(errno));
idx_tmp_name = xstrdup(tmpname);
@@ -1487,6 +1490,10 @@ static int git_pack_config(const char *k, const char *v)
window = git_config_int(k, v);
return 0;
}
if(!strcmp(k, "pack.depth")) {
depth = git_config_int(k, v);
return 0;
}
return git_default_config(k, v);
}
@@ -1582,7 +1589,6 @@ static int adjust_perm(const char *path, mode_t mode)
int cmd_pack_objects(int argc, const char **argv, const char *prefix)
{
int depth = 10;
int use_internal_rev_list = 0;
int thin = 0;
uint32_t i;

View File

@@ -133,7 +133,7 @@ static void add_to_msg(const char *string)
{
int len = strlen(string);
if (write_in_full(msg_fd, string, len) < 0)
die ("Could not write to .msg");
die ("Could not write to MERGE_MSG");
}
static void add_message_to_msg(const char *message)
@@ -237,6 +237,7 @@ static int revert_or_cherry_pick(int argc, const char **argv)
int i;
char *oneline, *reencoded_message = NULL;
const char *message, *encoding;
const char *defmsg = xstrdup(git_path("MERGE_MSG"));
git_config(git_default_config);
me = action == REVERT ? "revert" : "cherry-pick";
@@ -280,7 +281,7 @@ static int revert_or_cherry_pick(int argc, const char **argv)
* reverse of it if we are revert.
*/
msg_fd = hold_lock_file_for_update(&msg_file, ".msg", 1);
msg_fd = hold_lock_file_for_update(&msg_file, defmsg, 1);
encoding = get_encoding(message);
if (!encoding)
@@ -330,7 +331,6 @@ static int revert_or_cherry_pick(int argc, const char **argv)
sha1_to_hex(head), "HEAD",
sha1_to_hex(next->object.sha1), oneline) ||
write_tree(head, 0, NULL)) {
const char *target = git_path("MERGE_MSG");
add_to_msg("\nConflicts:\n\n");
read_cache();
for (i = 0; i < active_nr;) {
@@ -345,10 +345,7 @@ static int revert_or_cherry_pick(int argc, const char **argv)
}
}
if (close(msg_fd) || commit_lock_file(&msg_file) < 0)
die ("Error wrapping up .msg");
unlink(target);
if (rename(".msg", target))
die ("Could not move .msg to %s", target);
die ("Error wrapping up %s", defmsg);
fprintf(stderr, "Automatic %s failed. "
"After resolving the conflicts,\n"
"mark the corrected paths with 'git-add <paths>'\n"
@@ -362,7 +359,7 @@ static int revert_or_cherry_pick(int argc, const char **argv)
exit(1);
}
if (close(msg_fd) || commit_lock_file(&msg_file) < 0)
die ("Error wrapping up .msg");
die ("Error wrapping up %s", defmsg);
fprintf(stderr, "Finished one %s.\n", me);
/*
@@ -376,11 +373,9 @@ static int revert_or_cherry_pick(int argc, const char **argv)
if (!no_commit) {
if (edit)
return execl_git_cmd("commit", "-n", "-F", ".msg",
"-e", NULL);
return execl_git_cmd("commit", "-n", NULL);
else
return execl_git_cmd("commit", "-n", "-F", ".msg",
NULL);
return execl_git_cmd("commit", "-n", "-F", defmsg, NULL);
}
if (reencoded_message)
free(reencoded_message);

View File

@@ -51,7 +51,7 @@ foreach my $tar_file (@ARGV)
$prefix) = unpack 'Z100 Z8 Z8 Z8 Z12 Z12
Z8 Z1 Z100 Z6
Z2 Z32 Z32 Z8 Z8 Z*', $_;
last unless $name;
last unless length($name);
if ($name eq '././@LongLink') {
# GNU tar extension
if (read(I, $_, 512) != 512) {

94
diff.c
View File

@@ -17,8 +17,6 @@
#define FAST_WORKING_DIRECTORY 1
#endif
static int use_size_cache;
static int diff_detect_rename_default;
static int diff_rename_limit_default = -1;
static int diff_use_color_default;
@@ -1237,6 +1235,8 @@ static void builtin_diff(const char *name_a,
}
free_ab_and_return:
diff_free_filespec_data(one);
diff_free_filespec_data(two);
free(a_one);
free(b_two);
return;
@@ -1263,7 +1263,7 @@ static void builtin_diffstat(const char *name_a, const char *name_b,
diff_populate_filespec(two, 0);
data->deleted = count_lines(one->data, one->size);
data->added = count_lines(two->data, two->size);
return;
goto free_and_return;
}
if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
die("unable to read files to diff");
@@ -1285,6 +1285,10 @@ static void builtin_diffstat(const char *name_a, const char *name_b,
ecb.priv = diffstat;
xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
}
free_and_return:
diff_free_filespec_data(one);
diff_free_filespec_data(two);
}
static void builtin_checkdiff(const char *name_a, const char *name_b,
@@ -1307,7 +1311,7 @@ static void builtin_checkdiff(const char *name_a, const char *name_b,
die("unable to read files to diff");
if (file_is_binary(two))
return;
goto free_and_return;
else {
/* Crazy xdl interfaces.. */
xpparam_t xpp;
@@ -1321,6 +1325,9 @@ static void builtin_checkdiff(const char *name_a, const char *name_b,
ecb.priv = &data;
xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
}
free_and_return:
diff_free_filespec_data(one);
diff_free_filespec_data(two);
}
struct diff_filespec *alloc_filespec(const char *path)
@@ -1400,55 +1407,6 @@ static int reuse_worktree_file(const char *name, const unsigned char *sha1, int
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 = hashcmp(e->sha1, sha1);
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;
hashcpy(e->sha1, sha1);
e->size = size;
return e;
}
static int populate_from_stdin(struct diff_filespec *s)
{
#define INCREMENT 1024
@@ -1504,11 +1462,11 @@ int diff_populate_filespec(struct diff_filespec *s, int size_only)
if (S_ISDIR(s->mode))
return -1;
if (!use_size_cache)
size_only = 0;
if (s->data)
return err;
return 0;
if (size_only && 0 < s->size)
return 0;
if (S_ISDIRLNK(s->mode))
return diff_populate_gitlink(s, size_only);
@@ -1571,19 +1529,8 @@ int diff_populate_filespec(struct diff_filespec *s, int size_only)
}
else {
enum object_type type;
struct sha1_size_cache *e;
if (size_only && use_size_cache &&
(e = locate_size_cache(s->sha1, 1, 0)) != NULL) {
s->size = e->size;
return 0;
}
if (size_only) {
if (size_only)
type = sha1_object_info(s->sha1, &s->size);
if (use_size_cache && 0 < type)
locate_size_cache(s->sha1, 0, s->size);
}
else {
s->data = read_sha1_file(s->sha1, &type, &s->size);
s->should_free = 1;
@@ -1598,8 +1545,11 @@ void diff_free_filespec_data(struct diff_filespec *s)
free(s->data);
else if (s->should_munmap)
munmap(s->data, s->size);
s->should_free = s->should_munmap = 0;
s->data = NULL;
if (s->should_free || s->should_munmap) {
s->should_free = s->should_munmap = 0;
s->data = NULL;
}
free(s->cnt_data);
s->cnt_data = NULL;
}
@@ -2085,8 +2035,6 @@ int diff_setup_done(struct diff_options *options)
*/
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 */

View File

@@ -44,6 +44,7 @@ static unsigned int contains(struct diff_filespec *one,
}
}
}
diff_free_filespec_data(one);
return cnt;
}

View File

@@ -329,6 +329,7 @@ void diffcore_rename(struct diff_options *options)
m->dst = i;
m->score = estimate_similarity(one, two,
minimum_score);
diff_free_filespec_data(one);
}
/* We do not need the text anymore */
diff_free_filespec_data(two);

7
dir.c
View File

@@ -448,6 +448,7 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co
while ((de = readdir(fdir)) != NULL) {
int len;
int exclude;
if ((de->d_name[0] == '.') &&
(de->d_name[1] == 0 ||
@@ -461,7 +462,9 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co
memcpy(fullname + baselen, de->d_name, len+1);
if (simplify_away(fullname, baselen + len, simplify))
continue;
if (excluded(dir, fullname) != dir->show_ignored) {
exclude = excluded(dir, fullname);
if (exclude != dir->show_ignored) {
if (!dir->show_ignored || DTYPE(de) != DT_DIR) {
continue;
}
@@ -484,6 +487,8 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co
len++;
switch (treat_directory(dir, fullname, baselen + len, simplify)) {
case show_directory:
if (exclude != dir->show_ignored)
continue;
break;
case recurse_into_directory:
contents += read_directory_recursive(dir,

View File

@@ -18,7 +18,14 @@ usage() {
}
get_repo_base() {
(cd "$1" && (cd .git ; pwd)) 2> /dev/null
(
cd "`/bin/pwd`" &&
cd "$1" &&
{
cd .git 2>/dev/null
pwd
}
)
}
if [ -n "$GIT_SSL_NO_VERIFY" ]; then

2
git-gui/.gitignore vendored
View File

@@ -1,3 +1,5 @@
GIT-VERSION-FILE
GIT-GUI-VARS
git-citool
git-gui
lib/tclIndex

View File

@@ -1,7 +1,7 @@
#!/bin/sh
GVF=GIT-VERSION-FILE
DEF_VER=0.6.GITGUI
DEF_VER=0.7.GITGUI
LF='
'

View File

@@ -10,6 +10,7 @@ GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
SCRIPT_SH = git-gui.sh
GITGUI_BUILT_INS = git-citool
ALL_PROGRAMS = $(GITGUI_BUILT_INS) $(patsubst %.sh,%,$(SCRIPT_SH))
ALL_LIBFILES = $(wildcard lib/*.tcl)
ifndef SHELL_PATH
SHELL_PATH = /bin/sh
@@ -19,6 +20,10 @@ ifndef gitexecdir
gitexecdir := $(shell git --exec-path)
endif
ifndef sharedir
sharedir := $(dir $(gitexecdir))/share
endif
ifndef INSTALL
INSTALL = install
endif
@@ -26,8 +31,10 @@ endif
ifndef V
QUIET_GEN = @echo ' ' GEN $@;
QUIET_BUILT_IN = @echo ' ' BUILTIN $@;
QUIET_INDEX = @echo ' ' INDEX $(dir $@);
endif
TCL_PATH ?= tclsh
TCLTK_PATH ?= wish
ifeq ($(findstring $(MAKEFLAGS),s),s)
@@ -40,11 +47,15 @@ gitexecdir_SQ = $(subst ','\'',$(gitexecdir))
SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_PATH))
libdir ?= $(sharedir)/git-gui/lib
libdir_SQ = $(subst ','\'',$(libdir))
$(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh
$(QUIET_GEN)rm -f $@ $@+ && \
sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
-e 's|^exec wish "$$0"|exec $(subst |,'\|',$(TCLTK_PATH_SQ)) "$$0"|' \
-e 's/@@GITGUI_VERSION@@/$(GITGUI_VERSION)/g' \
-e 's|@@GITGUI_LIBDIR@@|$(libdir_SQ)|' \
$@.sh >$@+ && \
chmod +x $@+ && \
mv $@+ $@
@@ -52,22 +63,46 @@ $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh
$(GITGUI_BUILT_INS): git-gui
$(QUIET_BUILT_IN)rm -f $@ && ln git-gui $@
# These can record GITGUI_VERSION
$(patsubst %.sh,%,$(SCRIPT_SH)): GIT-VERSION-FILE
lib/tclIndex: $(ALL_LIBFILES)
$(QUIET_INDEX)echo \
source lib/class.tcl \; \
auto_mkindex lib '*.tcl' \
| $(TCL_PATH)
all:: $(ALL_PROGRAMS)
# These can record GITGUI_VERSION
$(patsubst %.sh,%,$(SCRIPT_SH)): GIT-VERSION-FILE GIT-GUI-VARS
TRACK_VARS = \
$(subst ','\'',SHELL_PATH='$(SHELL_PATH_SQ)') \
$(subst ','\'',TCLTK_PATH='$(TCLTK_PATH_SQ)') \
$(subst ','\'',libdir='$(libdir_SQ)') \
#end TRACK_VARS
GIT-GUI-VARS: .FORCE-GIT-GUI-VARS
@VARS='$(TRACK_VARS)'; \
if test x"$$VARS" != x"`cat $@ 2>/dev/null`" ; then \
echo 1>&2 " * new locations or Tcl/Tk interpreter"; \
echo 1>$@ "$$VARS"; \
fi
all:: $(ALL_PROGRAMS) lib/tclIndex
install: all
$(INSTALL) -d -m755 '$(DESTDIR_SQ)$(gitexecdir_SQ)'
$(INSTALL) git-gui '$(DESTDIR_SQ)$(gitexecdir_SQ)'
$(foreach p,$(GITGUI_BUILT_INS), rm -f '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' && ln '$(DESTDIR_SQ)$(gitexecdir_SQ)/git-gui' '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' ;)
$(INSTALL) -d -m755 '$(DESTDIR_SQ)$(libdir_SQ)'
$(INSTALL) -m644 lib/tclIndex '$(DESTDIR_SQ)$(libdir_SQ)'
$(foreach p,$(ALL_LIBFILES), $(INSTALL) -m644 $p '$(DESTDIR_SQ)$(libdir_SQ)' ;)
dist-version:
@mkdir -p $(TARDIR)
@echo $(GITGUI_VERSION) > $(TARDIR)/version
clean::
rm -f $(ALL_PROGRAMS) GIT-VERSION-FILE
rm -f $(ALL_PROGRAMS) lib/tclIndex
rm -f GIT-VERSION-FILE GIT-GUI-VARS
.PHONY: all install dist-version clean
.PHONY: .FORCE-GIT-VERSION-FILE
.PHONY: .FORCE-GIT-GUI-VARS

4176
git-gui/git-gui.sh Normal file → Executable file

File diff suppressed because it is too large Load Diff

416
git-gui/lib/blame.tcl Normal file
View File

@@ -0,0 +1,416 @@
# git-gui blame viewer
# Copyright (C) 2006, 2007 Shawn Pearce
class blame {
field commit ; # input commit to blame
field path ; # input filename to view in $commit
field w
field w_line
field w_load
field w_file
field w_cmit
field status
field highlight_line -1 ; # current line selected
field highlight_commit {} ; # sha1 of commit selected
field total_lines 0 ; # total length of file
field blame_lines 0 ; # number of lines computed
field commit_count 0 ; # number of commits in $commit_list
field commit_list {} ; # list of commit sha1 in receipt order
field order ; # array commit -> receipt order
field header ; # array commit,key -> header field
field line_commit ; # array line -> sha1 commit
field line_file ; # array line -> file name
field r_commit ; # commit currently being parsed
field r_orig_line ; # original line number
field r_final_line ; # final line number
field r_line_count ; # lines in this region
constructor new {i_commit i_path} {
set commit $i_commit
set path $i_path
make_toplevel top w
wm title $top "[appname] ([reponame]): File Viewer"
set status "Loading $commit:$path..."
label $w.path -text "$commit:$path" \
-anchor w \
-justify left \
-borderwidth 1 \
-relief sunken \
-font font_uibold
pack $w.path -side top -fill x
frame $w.out
text $w.out.loaded_t \
-background white -borderwidth 0 \
-state disabled \
-wrap none \
-height 40 \
-width 1 \
-font font_diff
$w.out.loaded_t tag conf annotated -background grey
text $w.out.linenumber_t \
-background white -borderwidth 0 \
-state disabled \
-wrap none \
-height 40 \
-width 5 \
-font font_diff
$w.out.linenumber_t tag conf linenumber -justify right
text $w.out.file_t \
-background white -borderwidth 0 \
-state disabled \
-wrap none \
-height 40 \
-width 80 \
-xscrollcommand [list $w.out.sbx set] \
-font font_diff
scrollbar $w.out.sbx -orient h -command [list $w.out.file_t xview]
scrollbar $w.out.sby -orient v \
-command [list scrollbar2many [list \
$w.out.loaded_t \
$w.out.linenumber_t \
$w.out.file_t \
] yview]
grid \
$w.out.linenumber_t \
$w.out.loaded_t \
$w.out.file_t \
$w.out.sby \
-sticky nsew
grid conf $w.out.sbx -column 2 -sticky we
grid columnconfigure $w.out 2 -weight 1
grid rowconfigure $w.out 0 -weight 1
pack $w.out -fill both -expand 1
label $w.status \
-textvariable @status \
-anchor w \
-justify left \
-borderwidth 1 \
-relief sunken
pack $w.status -side bottom -fill x
frame $w.cm
text $w.cm.t \
-background white -borderwidth 0 \
-state disabled \
-wrap none \
-height 10 \
-width 80 \
-xscrollcommand [list $w.cm.sbx set] \
-yscrollcommand [list $w.cm.sby set] \
-font font_diff
scrollbar $w.cm.sbx -orient h -command [list $w.cm.t xview]
scrollbar $w.cm.sby -orient v -command [list $w.cm.t yview]
pack $w.cm.sby -side right -fill y
pack $w.cm.sbx -side bottom -fill x
pack $w.cm.t -expand 1 -fill both
pack $w.cm -side bottom -fill x
menu $w.ctxm -tearoff 0
$w.ctxm add command \
-label "Copy Commit" \
-command [cb _copycommit]
set w_line $w.out.linenumber_t
set w_load $w.out.loaded_t
set w_file $w.out.file_t
set w_cmit $w.cm.t
foreach i [list \
$w.out.loaded_t \
$w.out.linenumber_t \
$w.out.file_t] {
$i tag conf in_sel \
-background [$i cget -foreground] \
-foreground [$i cget -background]
$i conf -yscrollcommand \
[list many2scrollbar [list \
$w.out.loaded_t \
$w.out.linenumber_t \
$w.out.file_t \
] yview $w.out.sby]
bind $i <Button-1> "[cb _click $i @%x,%y]; focus $i"
bind_button3 $i "
set cursorX %x
set cursorY %y
set cursorW %W
tk_popup $w.ctxm %X %Y
"
}
foreach i [list \
$w.out.loaded_t \
$w.out.linenumber_t \
$w.out.file_t \
$w.cm.t] {
bind $i <Key-Up> {catch {%W yview scroll -1 units};break}
bind $i <Key-Down> {catch {%W yview scroll 1 units};break}
bind $i <Key-Left> {catch {%W xview scroll -1 units};break}
bind $i <Key-Right> {catch {%W xview scroll 1 units};break}
bind $i <Key-k> {catch {%W yview scroll -1 units};break}
bind $i <Key-j> {catch {%W yview scroll 1 units};break}
bind $i <Key-h> {catch {%W xview scroll -1 units};break}
bind $i <Key-l> {catch {%W xview scroll 1 units};break}
bind $i <Control-Key-b> {catch {%W yview scroll -1 pages};break}
bind $i <Control-Key-f> {catch {%W yview scroll 1 pages};break}
}
bind $w.cm.t <Button-1> [list focus $w.cm.t]
bind $top <Visibility> [list focus $top]
bind $top <Destroy> [list delete_this $this]
if {$commit eq {}} {
set fd [open $path r]
} else {
set cmd [list git cat-file blob "$commit:$path"]
set fd [open "| $cmd" r]
}
fconfigure $fd -blocking 0 -translation lf -encoding binary
fileevent $fd readable [cb _read_file $fd]
}
method _read_file {fd} {
$w_load conf -state normal
$w_line conf -state normal
$w_file conf -state normal
while {[gets $fd line] >= 0} {
regsub "\r\$" $line {} line
incr total_lines
$w_load insert end "\n"
$w_line insert end "$total_lines\n" linenumber
$w_file insert end "$line\n"
}
$w_load conf -state disabled
$w_line conf -state disabled
$w_file conf -state disabled
if {[eof $fd]} {
close $fd
_status $this
set cmd [list git blame -M -C --incremental]
if {$commit eq {}} {
lappend cmd --contents $path
} else {
lappend cmd $commit
}
lappend cmd -- $path
set fd [open "| $cmd" r]
fconfigure $fd -blocking 0 -translation lf -encoding binary
fileevent $fd readable [cb _read_blame $fd]
}
} ifdeleted { catch {close $fd} }
method _read_blame {fd} {
while {[gets $fd line] >= 0} {
if {[regexp {^([a-z0-9]{40}) (\d+) (\d+) (\d+)$} $line line \
cmit original_line final_line line_count]} {
set r_commit $cmit
set r_orig_line $original_line
set r_final_line $final_line
set r_line_count $line_count
if {[catch {set g $order($cmit)}]} {
$w_line tag conf g$cmit
$w_file tag conf g$cmit
$w_line tag raise in_sel
$w_file tag raise in_sel
$w_file tag raise sel
set order($cmit) $commit_count
incr commit_count
lappend commit_list $cmit
}
} elseif {[string match {filename *} $line]} {
set file [string range $line 9 end]
set n $r_line_count
set lno $r_final_line
set cmit $r_commit
while {$n > 0} {
set lno_e "$lno.0 lineend + 1c"
if {[catch {set g g$line_commit($lno)}]} {
$w_load tag add annotated $lno.0 $lno_e
} else {
$w_line tag remove g$g $lno.0 $lno_e
$w_file tag remove g$g $lno.0 $lno_e
}
set line_commit($lno) $cmit
set line_file($lno) $file
$w_line tag add g$cmit $lno.0 $lno_e
$w_file tag add g$cmit $lno.0 $lno_e
if {$highlight_line == -1} {
if {[lindex [$w_file yview] 0] == 0} {
$w_file see $lno.0
_showcommit $this $lno
}
} elseif {$highlight_line == $lno} {
_showcommit $this $lno
}
incr n -1
incr lno
incr blame_lines
}
set hc $highlight_commit
if {$hc ne {}
&& [expr {$order($hc) + 1}] == $order($cmit)} {
_showcommit $this $highlight_line
}
} elseif {[regexp {^([a-z-]+) (.*)$} $line line key data]} {
set header($r_commit,$key) $data
}
}
if {[eof $fd]} {
close $fd
set status {Annotation complete.}
} else {
_status $this
}
} ifdeleted { catch {close $fd} }
method _status {} {
set have $blame_lines
set total $total_lines
set pdone 0
if {$total} {set pdone [expr {100 * $have / $total}]}
set status [format \
"Loading annotations... %i of %i lines annotated (%2i%%)" \
$have $total $pdone]
}
method _click {cur_w pos} {
set lno [lindex [split [$cur_w index $pos] .] 0]
if {$lno eq {}} return
set lno_e "$lno.0 + 1 line"
$w_line tag remove in_sel 0.0 end
$w_file tag remove in_sel 0.0 end
$w_line tag add in_sel $lno.0 $lno_e
$w_file tag add in_sel $lno.0 $lno_e
_showcommit $this $lno
}
variable blame_colors {
#ff4040
#ff40ff
#4040ff
}
method _showcommit {lno} {
global repo_config
variable blame_colors
if {$highlight_commit ne {}} {
set idx $order($highlight_commit)
set i 0
foreach c $blame_colors {
set h [lindex $commit_list [expr {$idx - 1 + $i}]]
$w_line tag conf g$h -background white
$w_file tag conf g$h -background white
incr i
}
}
$w_cmit conf -state normal
$w_cmit delete 0.0 end
if {[catch {set cmit $line_commit($lno)}]} {
set cmit {}
$w_cmit insert end "Loading annotation..."
} else {
set idx $order($cmit)
set i 0
foreach c $blame_colors {
set h [lindex $commit_list [expr {$idx - 1 + $i}]]
$w_line tag conf g$h -background $c
$w_file tag conf g$h -background $c
incr i
}
set author_name {}
set author_email {}
set author_time {}
catch {set author_name $header($cmit,author)}
catch {set author_email $header($cmit,author-mail)}
catch {set author_time [clock format \
$header($cmit,author-time) \
-format {%Y-%m-%d %H:%M:%S}
]}
set committer_name {}
set committer_email {}
set committer_time {}
catch {set committer_name $header($cmit,committer)}
catch {set committer_email $header($cmit,committer-mail)}
catch {set committer_time [clock format \
$header($cmit,committer-time) \
-format {%Y-%m-%d %H:%M:%S}
]}
if {[catch {set msg $header($cmit,message)}]} {
set msg {}
catch {
set fd [open "| git cat-file commit $cmit" r]
fconfigure $fd -encoding binary -translation lf
if {[catch {set enc $repo_config(i18n.commitencoding)}]} {
set enc utf-8
}
while {[gets $fd line] > 0} {
if {[string match {encoding *} $line]} {
set enc [string tolower [string range $line 9 end]]
}
}
set msg [encoding convertfrom $enc [read $fd]]
set msg [string trim $msg]
close $fd
set author_name [encoding convertfrom $enc $author_name]
set committer_name [encoding convertfrom $enc $committer_name]
set header($cmit,author) $author_name
set header($cmit,committer) $committer_name
}
set header($cmit,message) $msg
}
$w_cmit insert end "commit $cmit
Author: $author_name $author_email $author_time
Committer: $committer_name $committer_email $committer_time
Original File: [escape_path $line_file($lno)]
$msg"
}
$w_cmit conf -state disabled
set highlight_line $lno
set highlight_commit $cmit
}
method _copycommit {} {
set pos @$::cursorX,$::cursorY
set lno [lindex [split [$::cursorW index $pos] .] 0]
if {![catch {set commit $line_commit($lno)}]} {
clipboard clear
clipboard append \
-format STRING \
-type STRING \
-- $commit
}
}
}

572
git-gui/lib/branch.tcl Normal file
View File

@@ -0,0 +1,572 @@
# git-gui branch (create/delete) support
# Copyright (C) 2006, 2007 Shawn Pearce
proc load_all_heads {} {
global all_heads
set all_heads [list]
set fd [open "| git for-each-ref --format=%(refname) refs/heads" r]
while {[gets $fd line] > 0} {
if {[is_tracking_branch $line]} continue
if {![regsub ^refs/heads/ $line {} name]} continue
lappend all_heads $name
}
close $fd
set all_heads [lsort $all_heads]
}
proc load_all_tags {} {
set all_tags [list]
set fd [open "| git for-each-ref --format=%(refname) refs/tags" r]
while {[gets $fd line] > 0} {
if {![regsub ^refs/tags/ $line {} name]} continue
lappend all_tags $name
}
close $fd
return [lsort $all_tags]
}
proc populate_branch_menu {} {
global all_heads disable_on_lock
set m .mbar.branch
set last [$m index last]
for {set i 0} {$i <= $last} {incr i} {
if {[$m type $i] eq {separator}} {
$m delete $i last
set new_dol [list]
foreach a $disable_on_lock {
if {[lindex $a 0] ne $m || [lindex $a 2] < $i} {
lappend new_dol $a
}
}
set disable_on_lock $new_dol
break
}
}
if {$all_heads ne {}} {
$m add separator
}
foreach b $all_heads {
$m add radiobutton \
-label $b \
-command [list switch_branch $b] \
-variable current_branch \
-value $b
lappend disable_on_lock \
[list $m entryconf [$m index last] -state]
}
}
proc do_create_branch_action {w} {
global all_heads null_sha1 repo_config
global create_branch_checkout create_branch_revtype
global create_branch_head create_branch_trackinghead
global create_branch_name create_branch_revexp
global create_branch_tag
set newbranch $create_branch_name
if {$newbranch eq {}
|| $newbranch eq $repo_config(gui.newbranchtemplate)} {
tk_messageBox \
-icon error \
-type ok \
-title [wm title $w] \
-parent $w \
-message "Please supply a branch name."
focus $w.desc.name_t
return
}
if {![catch {git show-ref --verify -- "refs/heads/$newbranch"}]} {
tk_messageBox \
-icon error \
-type ok \
-title [wm title $w] \
-parent $w \
-message "Branch '$newbranch' already exists."
focus $w.desc.name_t
return
}
if {[catch {git check-ref-format "heads/$newbranch"}]} {
tk_messageBox \
-icon error \
-type ok \
-title [wm title $w] \
-parent $w \
-message "We do not like '$newbranch' as a branch name."
focus $w.desc.name_t
return
}
set rev {}
switch -- $create_branch_revtype {
head {set rev $create_branch_head}
tracking {set rev $create_branch_trackinghead}
tag {set rev $create_branch_tag}
expression {set rev $create_branch_revexp}
}
if {[catch {set cmt [git rev-parse --verify "${rev}^0"]}]} {
tk_messageBox \
-icon error \
-type ok \
-title [wm title $w] \
-parent $w \
-message "Invalid starting revision: $rev"
return
}
if {[catch {
git update-ref \
-m "branch: Created from $rev" \
"refs/heads/$newbranch" \
$cmt \
$null_sha1
} err]} {
tk_messageBox \
-icon error \
-type ok \
-title [wm title $w] \
-parent $w \
-message "Failed to create '$newbranch'.\n\n$err"
return
}
lappend all_heads $newbranch
set all_heads [lsort $all_heads]
populate_branch_menu
destroy $w
if {$create_branch_checkout} {
switch_branch $newbranch
}
}
proc radio_selector {varname value args} {
upvar #0 $varname var
set var $value
}
trace add variable create_branch_head write \
[list radio_selector create_branch_revtype head]
trace add variable create_branch_trackinghead write \
[list radio_selector create_branch_revtype tracking]
trace add variable create_branch_tag write \
[list radio_selector create_branch_revtype tag]
trace add variable delete_branch_head write \
[list radio_selector delete_branch_checktype head]
trace add variable delete_branch_trackinghead write \
[list radio_selector delete_branch_checktype tracking]
proc do_create_branch {} {
global all_heads current_branch repo_config
global create_branch_checkout create_branch_revtype
global create_branch_head create_branch_trackinghead
global create_branch_name create_branch_revexp
global create_branch_tag
set w .branch_editor
toplevel $w
wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
label $w.header -text {Create New Branch} \
-font font_uibold
pack $w.header -side top -fill x
frame $w.buttons
button $w.buttons.create -text Create \
-default active \
-command [list do_create_branch_action $w]
pack $w.buttons.create -side right
button $w.buttons.cancel -text {Cancel} \
-command [list destroy $w]
pack $w.buttons.cancel -side right -padx 5
pack $w.buttons -side bottom -fill x -pady 10 -padx 10
labelframe $w.desc -text {Branch Description}
label $w.desc.name_l -text {Name:}
entry $w.desc.name_t \
-borderwidth 1 \
-relief sunken \
-width 40 \
-textvariable create_branch_name \
-validate key \
-validatecommand {
if {%d == 1 && [regexp {[~^:?*\[\0- ]} %S]} {return 0}
return 1
}
grid $w.desc.name_l $w.desc.name_t -sticky we -padx {0 5}
grid columnconfigure $w.desc 1 -weight 1
pack $w.desc -anchor nw -fill x -pady 5 -padx 5
labelframe $w.from -text {Starting Revision}
radiobutton $w.from.head_r \
-text {Local Branch:} \
-value head \
-variable create_branch_revtype
eval tk_optionMenu $w.from.head_m create_branch_head $all_heads
grid $w.from.head_r $w.from.head_m -sticky w
set all_trackings [all_tracking_branches]
if {$all_trackings ne {}} {
set create_branch_trackinghead [lindex $all_trackings 0]
radiobutton $w.from.tracking_r \
-text {Tracking Branch:} \
-value tracking \
-variable create_branch_revtype
eval tk_optionMenu $w.from.tracking_m \
create_branch_trackinghead \
$all_trackings
grid $w.from.tracking_r $w.from.tracking_m -sticky w
}
set all_tags [load_all_tags]
if {$all_tags ne {}} {
set create_branch_tag [lindex $all_tags 0]
radiobutton $w.from.tag_r \
-text {Tag:} \
-value tag \
-variable create_branch_revtype
eval tk_optionMenu $w.from.tag_m create_branch_tag $all_tags
grid $w.from.tag_r $w.from.tag_m -sticky w
}
radiobutton $w.from.exp_r \
-text {Revision Expression:} \
-value expression \
-variable create_branch_revtype
entry $w.from.exp_t \
-borderwidth 1 \
-relief sunken \
-width 50 \
-textvariable create_branch_revexp \
-validate key \
-validatecommand {
if {%d == 1 && [regexp {\s} %S]} {return 0}
if {%d == 1 && [string length %S] > 0} {
set create_branch_revtype expression
}
return 1
}
grid $w.from.exp_r $w.from.exp_t -sticky we -padx {0 5}
grid columnconfigure $w.from 1 -weight 1
pack $w.from -anchor nw -fill x -pady 5 -padx 5
labelframe $w.postActions -text {Post Creation Actions}
checkbutton $w.postActions.checkout \
-text {Checkout after creation} \
-variable create_branch_checkout
pack $w.postActions.checkout -anchor nw
pack $w.postActions -anchor nw -fill x -pady 5 -padx 5
set create_branch_checkout 1
set create_branch_head $current_branch
set create_branch_revtype head
set create_branch_name $repo_config(gui.newbranchtemplate)
set create_branch_revexp {}
bind $w <Visibility> "
grab $w
$w.desc.name_t icursor end
focus $w.desc.name_t
"
bind $w <Key-Escape> "destroy $w"
bind $w <Key-Return> "do_create_branch_action $w;break"
wm title $w "[appname] ([reponame]): Create Branch"
tkwait window $w
}
proc do_delete_branch_action {w} {
global all_heads
global delete_branch_checktype delete_branch_head delete_branch_trackinghead
set check_rev {}
switch -- $delete_branch_checktype {
head {set check_rev $delete_branch_head}
tracking {set check_rev $delete_branch_trackinghead}
always {set check_rev {:none}}
}
if {$check_rev eq {:none}} {
set check_cmt {}
} elseif {[catch {set check_cmt [git rev-parse --verify "${check_rev}^0"]}]} {
tk_messageBox \
-icon error \
-type ok \
-title [wm title $w] \
-parent $w \
-message "Invalid check revision: $check_rev"
return
}
set to_delete [list]
set not_merged [list]
foreach i [$w.list.l curselection] {
set b [$w.list.l get $i]
if {[catch {set o [git rev-parse --verify $b]}]} continue
if {$check_cmt ne {}} {
if {$b eq $check_rev} continue
if {[catch {set m [git merge-base $o $check_cmt]}]} continue
if {$o ne $m} {
lappend not_merged $b
continue
}
}
lappend to_delete [list $b $o]
}
if {$not_merged ne {}} {
set msg "The following branches are not completely merged into $check_rev:
- [join $not_merged "\n - "]"
tk_messageBox \
-icon info \
-type ok \
-title [wm title $w] \
-parent $w \
-message $msg
}
if {$to_delete eq {}} return
if {$delete_branch_checktype eq {always}} {
set msg {Recovering deleted branches is difficult.
Delete the selected branches?}
if {[tk_messageBox \
-icon warning \
-type yesno \
-title [wm title $w] \
-parent $w \
-message $msg] ne yes} {
return
}
}
set failed {}
foreach i $to_delete {
set b [lindex $i 0]
set o [lindex $i 1]
if {[catch {git update-ref -d "refs/heads/$b" $o} err]} {
append failed " - $b: $err\n"
} else {
set x [lsearch -sorted -exact $all_heads $b]
if {$x >= 0} {
set all_heads [lreplace $all_heads $x $x]
}
}
}
if {$failed ne {}} {
tk_messageBox \
-icon error \
-type ok \
-title [wm title $w] \
-parent $w \
-message "Failed to delete branches:\n$failed"
}
set all_heads [lsort $all_heads]
populate_branch_menu
destroy $w
}
proc do_delete_branch {} {
global all_heads tracking_branches current_branch
global delete_branch_checktype delete_branch_head delete_branch_trackinghead
set w .branch_editor
toplevel $w
wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
label $w.header -text {Delete Local Branch} \
-font font_uibold
pack $w.header -side top -fill x
frame $w.buttons
button $w.buttons.create -text Delete \
-command [list do_delete_branch_action $w]
pack $w.buttons.create -side right
button $w.buttons.cancel -text {Cancel} \
-command [list destroy $w]
pack $w.buttons.cancel -side right -padx 5
pack $w.buttons -side bottom -fill x -pady 10 -padx 10
labelframe $w.list -text {Local Branches}
listbox $w.list.l \
-height 10 \
-width 70 \
-selectmode extended \
-yscrollcommand [list $w.list.sby set]
foreach h $all_heads {
if {$h ne $current_branch} {
$w.list.l insert end $h
}
}
scrollbar $w.list.sby -command [list $w.list.l yview]
pack $w.list.sby -side right -fill y
pack $w.list.l -side left -fill both -expand 1
pack $w.list -fill both -expand 1 -pady 5 -padx 5
labelframe $w.validate -text {Delete Only If}
radiobutton $w.validate.head_r \
-text {Merged Into Local Branch:} \
-value head \
-variable delete_branch_checktype
eval tk_optionMenu $w.validate.head_m delete_branch_head $all_heads
grid $w.validate.head_r $w.validate.head_m -sticky w
set all_trackings [all_tracking_branches]
if {$all_trackings ne {}} {
set delete_branch_trackinghead [lindex $all_trackings 0]
radiobutton $w.validate.tracking_r \
-text {Merged Into Tracking Branch:} \
-value tracking \
-variable delete_branch_checktype
eval tk_optionMenu $w.validate.tracking_m \
delete_branch_trackinghead \
$all_trackings
grid $w.validate.tracking_r $w.validate.tracking_m -sticky w
}
radiobutton $w.validate.always_r \
-text {Always (Do not perform merge checks)} \
-value always \
-variable delete_branch_checktype
grid $w.validate.always_r -columnspan 2 -sticky w
grid columnconfigure $w.validate 1 -weight 1
pack $w.validate -anchor nw -fill x -pady 5 -padx 5
set delete_branch_head $current_branch
set delete_branch_checktype head
bind $w <Visibility> "grab $w; focus $w"
bind $w <Key-Escape> "destroy $w"
wm title $w "[appname] ([reponame]): Delete Branch"
tkwait window $w
}
proc switch_branch {new_branch} {
global HEAD commit_type current_branch repo_config
if {![lock_index switch]} return
# -- Our in memory state should match the repository.
#
repository_state curType curHEAD curMERGE_HEAD
if {[string match amend* $commit_type]
&& $curType eq {normal}
&& $curHEAD eq $HEAD} {
} elseif {$commit_type ne $curType || $HEAD ne $curHEAD} {
info_popup {Last scanned state does not match repository state.
Another Git program has modified this repository since the last scan. A rescan must be performed before the current branch can be changed.
The rescan will be automatically started now.
}
unlock_index
rescan {set ui_status_value {Ready.}}
return
}
# -- Don't do a pointless switch.
#
if {$current_branch eq $new_branch} {
unlock_index
return
}
if {$repo_config(gui.trustmtime) eq {true}} {
switch_branch_stage2 {} $new_branch
} else {
set ui_status_value {Refreshing file status...}
set cmd [list git update-index]
lappend cmd -q
lappend cmd --unmerged
lappend cmd --ignore-missing
lappend cmd --refresh
set fd_rf [open "| $cmd" r]
fconfigure $fd_rf -blocking 0 -translation binary
fileevent $fd_rf readable \
[list switch_branch_stage2 $fd_rf $new_branch]
}
}
proc switch_branch_stage2 {fd_rf new_branch} {
global ui_status_value HEAD
if {$fd_rf ne {}} {
read $fd_rf
if {![eof $fd_rf]} return
close $fd_rf
}
set ui_status_value "Updating working directory to '$new_branch'..."
set cmd [list git read-tree]
lappend cmd -m
lappend cmd -u
lappend cmd --exclude-per-directory=.gitignore
lappend cmd $HEAD
lappend cmd $new_branch
set fd_rt [open "| $cmd" r]
fconfigure $fd_rt -blocking 0 -translation binary
fileevent $fd_rt readable \
[list switch_branch_readtree_wait $fd_rt $new_branch]
}
proc switch_branch_readtree_wait {fd_rt new_branch} {
global selected_commit_type commit_type HEAD MERGE_HEAD PARENT
global current_branch
global ui_comm ui_status_value
# -- We never get interesting output on stdout; only stderr.
#
read $fd_rt
fconfigure $fd_rt -blocking 1
if {![eof $fd_rt]} {
fconfigure $fd_rt -blocking 0
return
}
# -- The working directory wasn't in sync with the index and
# we'd have to overwrite something to make the switch. A
# merge is required.
#
if {[catch {close $fd_rt} err]} {
regsub {^fatal: } $err {} err
warn_popup "File level merge required.
$err
Staying on branch '$current_branch'."
set ui_status_value "Aborted checkout of '$new_branch' (file level merging is required)."
unlock_index
return
}
# -- Update the symbolic ref. Core git doesn't even check for failure
# here, it Just Works(tm). If it doesn't we are in some really ugly
# state that is difficult to recover from within git-gui.
#
if {[catch {git symbolic-ref HEAD "refs/heads/$new_branch"} err]} {
error_popup "Failed to set current branch.
This working directory is only partially switched. We successfully updated your files, but failed to update an internal Git file.
This should not have occurred. [appname] will now close and give up.
$err"
do_quit
return
}
# -- Update our repository state. If we were previously in amend mode
# we need to toss the current buffer and do a full rescan to update
# our file lists. If we weren't in amend mode our file lists are
# accurate and we can avoid the rescan.
#
unlock_index
set selected_commit_type new
if {[string match amend* $commit_type]} {
$ui_comm delete 0.0 end
$ui_comm edit reset
$ui_comm edit modified false
rescan {set ui_status_value "Checked out branch '$current_branch'."}
} else {
repository_state commit_type HEAD MERGE_HEAD
set PARENT $HEAD
set ui_status_value "Checked out branch '$current_branch'."
}
}

239
git-gui/lib/browser.tcl Normal file
View File

@@ -0,0 +1,239 @@
# git-gui tree browser
# Copyright (C) 2006, 2007 Shawn Pearce
class browser {
field w
field browser_commit
field browser_path
field browser_files {}
field browser_status {Starting...}
field browser_stack {}
field browser_busy 1
constructor new {commit} {
global cursor_ptr M1B
make_toplevel top w
wm title $top "[appname] ([reponame]): File Browser"
set browser_commit $commit
set browser_path $browser_commit:
label $w.path \
-textvariable @browser_path \
-anchor w \
-justify left \
-borderwidth 1 \
-relief sunken \
-font font_uibold
pack $w.path -anchor w -side top -fill x
frame $w.list
set w_list $w.list.l
text $w_list -background white -borderwidth 0 \
-cursor $cursor_ptr \
-state disabled \
-wrap none \
-height 20 \
-width 70 \
-xscrollcommand [list $w.list.sbx set] \
-yscrollcommand [list $w.list.sby set]
$w_list tag conf in_sel \
-background [$w_list cget -foreground] \
-foreground [$w_list cget -background]
scrollbar $w.list.sbx -orient h -command [list $w_list xview]
scrollbar $w.list.sby -orient v -command [list $w_list yview]
pack $w.list.sbx -side bottom -fill x
pack $w.list.sby -side right -fill y
pack $w_list -side left -fill both -expand 1
pack $w.list -side top -fill both -expand 1
label $w.status \
-textvariable @browser_status \
-anchor w \
-justify left \
-borderwidth 1 \
-relief sunken
pack $w.status -anchor w -side bottom -fill x
bind $w_list <Button-1> "[cb _click 0 @%x,%y];break"
bind $w_list <Double-Button-1> "[cb _click 1 @%x,%y];break"
bind $w_list <$M1B-Up> "[cb _parent] ;break"
bind $w_list <$M1B-Left> "[cb _parent] ;break"
bind $w_list <Up> "[cb _move -1] ;break"
bind $w_list <Down> "[cb _move 1] ;break"
bind $w_list <$M1B-Right> "[cb _enter] ;break"
bind $w_list <Return> "[cb _enter] ;break"
bind $w_list <Prior> "[cb _page -1] ;break"
bind $w_list <Next> "[cb _page 1] ;break"
bind $w_list <Left> break
bind $w_list <Right> break
bind $w_list <Visibility> [list focus $w_list]
bind $w_list <Destroy> [list delete_this $this]
set w $w_list
_ls $this $browser_commit
return $this
}
method _move {dir} {
if {$browser_busy} return
set lno [lindex [split [$w index in_sel.first] .] 0]
incr lno $dir
if {[lindex $browser_files [expr {$lno - 1}]] ne {}} {
$w tag remove in_sel 0.0 end
$w tag add in_sel $lno.0 [expr {$lno + 1}].0
$w see $lno.0
}
}
method _page {dir} {
if {$browser_busy} return
$w yview scroll $dir pages
set lno [expr {int(
[lindex [$w yview] 0]
* [llength $browser_files]
+ 1)}]
if {[lindex $browser_files [expr {$lno - 1}]] ne {}} {
$w tag remove in_sel 0.0 end
$w tag add in_sel $lno.0 [expr {$lno + 1}].0
$w see $lno.0
}
}
method _parent {} {
if {$browser_busy} return
set info [lindex $browser_files 0]
if {[lindex $info 0] eq {parent}} {
set parent [lindex $browser_stack end-1]
set browser_stack [lrange $browser_stack 0 end-2]
if {$browser_stack eq {}} {
regsub {:.*$} $browser_path {:} browser_path
} else {
regsub {/[^/]+$} $browser_path {} browser_path
}
set browser_status "Loading $browser_path..."
_ls $this [lindex $parent 0] [lindex $parent 1]
}
}
method _enter {} {
if {$browser_busy} return
set lno [lindex [split [$w index in_sel.first] .] 0]
set info [lindex $browser_files [expr {$lno - 1}]]
if {$info ne {}} {
switch -- [lindex $info 0] {
parent {
_parent $this
}
tree {
set name [lindex $info 2]
set escn [escape_path $name]
set browser_status "Loading $escn..."
append browser_path $escn
_ls $this [lindex $info 1] $name
}
blob {
set name [lindex $info 2]
set p {}
foreach n $browser_stack {
append p [lindex $n 1]
}
append p $name
blame::new $browser_commit $p
}
}
}
}
method _click {was_double_click pos} {
if {$browser_busy} return
set lno [lindex [split [$w index $pos] .] 0]
focus $w
if {[lindex $browser_files [expr {$lno - 1}]] ne {}} {
$w tag remove in_sel 0.0 end
$w tag add in_sel $lno.0 [expr {$lno + 1}].0
if {$was_double_click} {
_enter $this
}
}
}
method _ls {tree_id {name {}}} {
set browser_buffer {}
set browser_files {}
set browser_busy 1
$w conf -state normal
$w tag remove in_sel 0.0 end
$w delete 0.0 end
if {$browser_stack ne {}} {
$w image create end \
-align center -padx 5 -pady 1 \
-name icon0 \
-image file_uplevel
$w insert end {[Up To Parent]}
lappend browser_files parent
}
lappend browser_stack [list $tree_id $name]
$w conf -state disabled
set cmd [list git ls-tree -z $tree_id]
set fd [open "| $cmd" r]
fconfigure $fd -blocking 0 -translation binary -encoding binary
fileevent $fd readable [cb _read $fd]
}
method _read {fd} {
append browser_buffer [read $fd]
set pck [split $browser_buffer "\0"]
set browser_buffer [lindex $pck end]
set n [llength $browser_files]
$w conf -state normal
foreach p [lrange $pck 0 end-1] {
set info [split $p "\t"]
set path [lindex $info 1]
set info [split [lindex $info 0] { }]
set type [lindex $info 1]
set object [lindex $info 2]
switch -- $type {
blob {
set image file_mod
}
tree {
set image file_dir
append path /
}
default {
set image file_question
}
}
if {$n > 0} {$w insert end "\n"}
$w image create end \
-align center -padx 5 -pady 1 \
-name icon[incr n] \
-image $image
$w insert end [escape_path $path]
lappend browser_files [list $type $object $path]
}
$w conf -state disabled
if {[eof $fd]} {
close $fd
set browser_status Ready.
set browser_busy 0
unset browser_buffer
if {$n > 0} {
$w tag add in_sel 1.0 2.0
focus -force $w
}
}
} ifdeleted {
catch {close $fd}
}
}

154
git-gui/lib/class.tcl Normal file
View File

@@ -0,0 +1,154 @@
# git-gui simple class/object fake-alike
# Copyright (C) 2007 Shawn Pearce
proc class {class body} {
if {[namespace exists $class]} {
error "class $class already declared"
}
namespace eval $class {
variable __nextid 0
variable __sealed 0
variable __field_list {}
variable __field_array
proc cb {name args} {
upvar this this
set args [linsert $args 0 $name $this]
return [uplevel [list namespace code $args]]
}
}
namespace eval $class $body
}
proc field {name args} {
set class [uplevel {namespace current}]
variable ${class}::__sealed
variable ${class}::__field_array
switch [llength $args] {
0 { set new [list $name] }
1 { set new [list $name [lindex $args 0]] }
default { error "wrong # args: field name value?" }
}
if {$__sealed} {
error "class $class is sealed (cannot add new fields)"
}
if {[catch {set old $__field_array($name)}]} {
variable ${class}::__field_list
lappend __field_list $new
set __field_array($name) 1
} else {
error "field $name already declared"
}
}
proc constructor {name params body} {
set class [uplevel {namespace current}]
set ${class}::__sealed 1
variable ${class}::__field_list
set mbodyc {}
append mbodyc {set this } $class
append mbodyc {::__o[incr } $class {::__nextid]} \;
append mbodyc {namespace eval $this {}} \;
if {$__field_list ne {}} {
append mbodyc {upvar #0}
foreach n $__field_list {
set n [lindex $n 0]
append mbodyc { ${this}::} $n { } $n
regsub -all @$n\\M $body "\${this}::$n" body
}
append mbodyc \;
foreach n $__field_list {
if {[llength $n] == 2} {
append mbodyc \
{set } [lindex $n 0] { } [list [lindex $n 1]] \;
}
}
}
append mbodyc $body
namespace eval $class [list proc $name $params $mbodyc]
}
proc method {name params body {deleted {}} {del_body {}}} {
set class [uplevel {namespace current}]
set ${class}::__sealed 1
variable ${class}::__field_list
set params [linsert $params 0 this]
set mbodyc {}
switch $deleted {
{} {}
ifdeleted {
append mbodyc {if {![namespace exists $this]} }
append mbodyc \{ $del_body \; return \} \;
}
default {
error "wrong # args: method name args body (ifdeleted body)?"
}
}
set decl {}
foreach n $__field_list {
set n [lindex $n 0]
if {[regexp -- $n\\M $body]} {
if { [regexp -all -- $n\\M $body] == 1
&& [regexp -all -- \\\$$n\\M $body] == 1
&& [regexp -all -- \\\$$n\\( $body] == 0} {
regsub -all \\\$$n\\M $body "\[set \${this}::$n\]" body
} else {
append decl { ${this}::} $n { } $n
regsub -all @$n\\M $body "\${this}::$n" body
}
}
}
if {$decl ne {}} {
append mbodyc {upvar #0} $decl \;
}
append mbodyc $body
namespace eval $class [list proc $name $params $mbodyc]
}
proc delete_this {{t {}}} {
if {$t eq {}} {
upvar this this
set t $this
}
if {[namespace exists $t]} {namespace delete $t}
}
proc make_toplevel {t w} {
upvar $t top $w pfx
if {[winfo ismapped .]} {
upvar this this
regsub -all {::} $this {__} w
set top .$w
set pfx $top
toplevel $top
} else {
set top .
set pfx {}
}
}
## auto_mkindex support for class/constructor/method
##
auto_mkindex_parser::command class {name body} {
variable parser
variable contextStack
set contextStack [linsert $contextStack 0 $name]
$parser eval [list _%@namespace eval $name] $body
set contextStack [lrange $contextStack 1 end]
}
auto_mkindex_parser::command constructor {name args} {
variable index
variable scriptFile
append index [list set auto_index([fullname $name])] \
[format { [list source [file join $dir %s]]} \
[file split $scriptFile]] "\n"
}

393
git-gui/lib/commit.tcl Normal file
View File

@@ -0,0 +1,393 @@
# git-gui misc. commit reading/writing support
# Copyright (C) 2006, 2007 Shawn Pearce
proc load_last_commit {} {
global HEAD PARENT MERGE_HEAD commit_type ui_comm
global repo_config
if {[llength $PARENT] == 0} {
error_popup {There is nothing to amend.
You are about to create the initial commit. There is no commit before this to amend.
}
return
}
repository_state curType curHEAD curMERGE_HEAD
if {$curType eq {merge}} {
error_popup {Cannot amend while merging.
You are currently in the middle of a merge that has not been fully completed. You cannot amend the prior commit unless you first abort the current merge activity.
}
return
}
set msg {}
set parents [list]
if {[catch {
set fd [open "| git cat-file commit $curHEAD" r]
fconfigure $fd -encoding binary -translation lf
if {[catch {set enc $repo_config(i18n.commitencoding)}]} {
set enc utf-8
}
while {[gets $fd line] > 0} {
if {[string match {parent *} $line]} {
lappend parents [string range $line 7 end]
} elseif {[string match {encoding *} $line]} {
set enc [string tolower [string range $line 9 end]]
}
}
set msg [encoding convertfrom $enc [read $fd]]
set msg [string trim $msg]
close $fd
} err]} {
error_popup "Error loading commit data for amend:\n\n$err"
return
}
set HEAD $curHEAD
set PARENT $parents
set MERGE_HEAD [list]
switch -- [llength $parents] {
0 {set commit_type amend-initial}
1 {set commit_type amend}
default {set commit_type amend-merge}
}
$ui_comm delete 0.0 end
$ui_comm insert end $msg
$ui_comm edit reset
$ui_comm edit modified false
rescan {set ui_status_value {Ready.}}
}
set GIT_COMMITTER_IDENT {}
proc committer_ident {} {
global GIT_COMMITTER_IDENT
if {$GIT_COMMITTER_IDENT eq {}} {
if {[catch {set me [git var GIT_COMMITTER_IDENT]} err]} {
error_popup "Unable to obtain your identity:\n\n$err"
return {}
}
if {![regexp {^(.*) [0-9]+ [-+0-9]+$} \
$me me GIT_COMMITTER_IDENT]} {
error_popup "Invalid GIT_COMMITTER_IDENT:\n\n$me"
return {}
}
}
return $GIT_COMMITTER_IDENT
}
proc do_signoff {} {
global ui_comm
set me [committer_ident]
if {$me eq {}} return
set sob "Signed-off-by: $me"
set last [$ui_comm get {end -1c linestart} {end -1c}]
if {$last ne $sob} {
$ui_comm edit separator
if {$last ne {}
&& ![regexp {^[A-Z][A-Za-z]*-[A-Za-z-]+: *} $last]} {
$ui_comm insert end "\n"
}
$ui_comm insert end "\n$sob"
$ui_comm edit separator
$ui_comm see end
}
}
proc create_new_commit {} {
global commit_type ui_comm
set commit_type normal
$ui_comm delete 0.0 end
$ui_comm edit reset
$ui_comm edit modified false
rescan {set ui_status_value {Ready.}}
}
proc commit_tree {} {
global HEAD commit_type file_states ui_comm repo_config
global ui_status_value pch_error
if {[committer_ident] eq {}} return
if {![lock_index update]} return
# -- Our in memory state should match the repository.
#
repository_state curType curHEAD curMERGE_HEAD
if {[string match amend* $commit_type]
&& $curType eq {normal}
&& $curHEAD eq $HEAD} {
} elseif {$commit_type ne $curType || $HEAD ne $curHEAD} {
info_popup {Last scanned state does not match repository state.
Another Git program has modified this repository since the last scan. A rescan must be performed before another commit can be created.
The rescan will be automatically started now.
}
unlock_index
rescan {set ui_status_value {Ready.}}
return
}
# -- At least one file should differ in the index.
#
set files_ready 0
foreach path [array names file_states] {
switch -glob -- [lindex $file_states($path) 0] {
_? {continue}
A? -
D? -
M? {set files_ready 1}
U? {
error_popup "Unmerged files cannot be committed.
File [short_path $path] has merge conflicts. You must resolve them and add the file before committing.
"
unlock_index
return
}
default {
error_popup "Unknown file state [lindex $s 0] detected.
File [short_path $path] cannot be committed by this program.
"
}
}
}
if {!$files_ready && ![string match *merge $curType]} {
info_popup {No changes to commit.
You must add at least 1 file before you can commit.
}
unlock_index
return
}
# -- A message is required.
#
set msg [string trim [$ui_comm get 1.0 end]]
regsub -all -line {[ \t\r]+$} $msg {} msg
if {$msg eq {}} {
error_popup {Please supply a commit message.
A good commit message has the following format:
- First line: Describe in one sentance what you did.
- Second line: Blank
- Remaining lines: Describe why this change is good.
}
unlock_index
return
}
# -- Run the pre-commit hook.
#
set pchook [gitdir hooks pre-commit]
# On Cygwin [file executable] might lie so we need to ask
# the shell if the hook is executable. Yes that's annoying.
#
if {[is_Cygwin] && [file isfile $pchook]} {
set pchook [list sh -c [concat \
"if test -x \"$pchook\";" \
"then exec \"$pchook\" 2>&1;" \
"fi"]]
} elseif {[file executable $pchook]} {
set pchook [list $pchook |& cat]
} else {
commit_writetree $curHEAD $msg
return
}
set ui_status_value {Calling pre-commit hook...}
set pch_error {}
set fd_ph [open "| $pchook" r]
fconfigure $fd_ph -blocking 0 -translation binary
fileevent $fd_ph readable \
[list commit_prehook_wait $fd_ph $curHEAD $msg]
}
proc commit_prehook_wait {fd_ph curHEAD msg} {
global pch_error ui_status_value
append pch_error [read $fd_ph]
fconfigure $fd_ph -blocking 1
if {[eof $fd_ph]} {
if {[catch {close $fd_ph}]} {
set ui_status_value {Commit declined by pre-commit hook.}
hook_failed_popup pre-commit $pch_error
unlock_index
} else {
commit_writetree $curHEAD $msg
}
set pch_error {}
return
}
fconfigure $fd_ph -blocking 0
}
proc commit_writetree {curHEAD msg} {
global ui_status_value
set ui_status_value {Committing changes...}
set fd_wt [open "| git write-tree" r]
fileevent $fd_wt readable \
[list commit_committree $fd_wt $curHEAD $msg]
}
proc commit_committree {fd_wt curHEAD msg} {
global HEAD PARENT MERGE_HEAD commit_type
global all_heads current_branch
global ui_status_value ui_comm selected_commit_type
global file_states selected_paths rescan_active
global repo_config
gets $fd_wt tree_id
if {$tree_id eq {} || [catch {close $fd_wt} err]} {
error_popup "write-tree failed:\n\n$err"
set ui_status_value {Commit failed.}
unlock_index
return
}
# -- Build the message.
#
set msg_p [gitdir COMMIT_EDITMSG]
set msg_wt [open $msg_p w]
if {[catch {set enc $repo_config(i18n.commitencoding)}]} {
set enc utf-8
}
fconfigure $msg_wt -encoding binary -translation binary
puts -nonewline $msg_wt [encoding convertto $enc $msg]
close $msg_wt
# -- Create the commit.
#
set cmd [list commit-tree $tree_id]
foreach p [concat $PARENT $MERGE_HEAD] {
lappend cmd -p $p
}
lappend cmd <$msg_p
if {[catch {set cmt_id [eval git $cmd]} err]} {
error_popup "commit-tree failed:\n\n$err"
set ui_status_value {Commit failed.}
unlock_index
return
}
# -- Update the HEAD ref.
#
set reflogm commit
if {$commit_type ne {normal}} {
append reflogm " ($commit_type)"
}
set i [string first "\n" $msg]
if {$i >= 0} {
set subject [string range $msg 0 [expr {$i - 1}]]
} else {
set subject $msg
}
append reflogm {: } $subject
if {[catch {
git update-ref -m $reflogm HEAD $cmt_id $curHEAD
} err]} {
error_popup "update-ref failed:\n\n$err"
set ui_status_value {Commit failed.}
unlock_index
return
}
# -- Cleanup after ourselves.
#
catch {file delete $msg_p}
catch {file delete [gitdir MERGE_HEAD]}
catch {file delete [gitdir MERGE_MSG]}
catch {file delete [gitdir SQUASH_MSG]}
catch {file delete [gitdir GITGUI_MSG]}
# -- Let rerere do its thing.
#
if {[file isdirectory [gitdir rr-cache]]} {
catch {git rerere}
}
# -- Run the post-commit hook.
#
set pchook [gitdir hooks post-commit]
if {[is_Cygwin] && [file isfile $pchook]} {
set pchook [list sh -c [concat \
"if test -x \"$pchook\";" \
"then exec \"$pchook\";" \
"fi"]]
} elseif {![file executable $pchook]} {
set pchook {}
}
if {$pchook ne {}} {
catch {exec $pchook &}
}
$ui_comm delete 0.0 end
$ui_comm edit reset
$ui_comm edit modified false
if {[is_enabled singlecommit]} do_quit
# -- Make sure our current branch exists.
#
if {$commit_type eq {initial}} {
lappend all_heads $current_branch
set all_heads [lsort -unique $all_heads]
populate_branch_menu
}
# -- Update in memory status
#
set selected_commit_type new
set commit_type normal
set HEAD $cmt_id
set PARENT $cmt_id
set MERGE_HEAD [list]
foreach path [array names file_states] {
set s $file_states($path)
set m [lindex $s 0]
switch -glob -- $m {
_O -
_M -
_D {continue}
__ -
A_ -
M_ -
D_ {
unset file_states($path)
catch {unset selected_paths($path)}
}
DO {
set file_states($path) [list _O [lindex $s 1] {} {}]
}
AM -
AD -
MM -
MD {
set file_states($path) [list \
_[string index $m 1] \
[lindex $s 1] \
[lindex $s 3] \
{}]
}
}
}
display_all_files
unlock_index
reshow_diff
set ui_status_value \
"Created commit [string range $cmt_id 0 7]: $subject"
}

176
git-gui/lib/console.tcl Normal file
View File

@@ -0,0 +1,176 @@
# git-gui console support
# Copyright (C) 2006, 2007 Shawn Pearce
class console {
field t_short
field t_long
field w
field console_cr
constructor new {short_title long_title} {
set t_short $short_title
set t_long $long_title
_init $this
return $this
}
method _init {} {
global M1B
make_toplevel top w
wm title $top "[appname] ([reponame]): $t_short"
set console_cr 1.0
frame $w.m
label $w.m.l1 \
-textvariable @t_long \
-anchor w \
-justify left \
-font font_uibold
text $w.m.t \
-background white -borderwidth 1 \
-relief sunken \
-width 80 -height 10 \
-font font_diff \
-state disabled \
-yscrollcommand [list $w.m.sby set]
label $w.m.s -text {Working... please wait...} \
-anchor w \
-justify left \
-font font_uibold
scrollbar $w.m.sby -command [list $w.m.t yview]
pack $w.m.l1 -side top -fill x
pack $w.m.s -side bottom -fill x
pack $w.m.sby -side right -fill y
pack $w.m.t -side left -fill both -expand 1
pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
menu $w.ctxm -tearoff 0
$w.ctxm add command -label "Copy" \
-command "tk_textCopy $w.m.t"
$w.ctxm add command -label "Select All" \
-command "focus $w.m.t;$w.m.t tag add sel 0.0 end"
$w.ctxm add command -label "Copy All" \
-command "
$w.m.t tag add sel 0.0 end
tk_textCopy $w.m.t
$w.m.t tag remove sel 0.0 end
"
button $w.ok -text {Close} \
-state disabled \
-command "destroy $w"
pack $w.ok -side bottom -anchor e -pady 10 -padx 10
bind_button3 $w.m.t "tk_popup $w.ctxm %X %Y"
bind $w.m.t <$M1B-Key-a> "$w.m.t tag add sel 0.0 end;break"
bind $w.m.t <$M1B-Key-A> "$w.m.t tag add sel 0.0 end;break"
bind $w <Visibility> "focus $w"
}
method exec {cmd {after {}}} {
# -- Cygwin's Tcl tosses the enviroment when we exec our child.
# But most users need that so we have to relogin. :-(
#
if {[is_Cygwin]} {
set cmd [list sh --login -c "cd \"[pwd]\" && [join $cmd { }]"]
}
# -- Tcl won't let us redirect both stdout and stderr to
# the same pipe. So pass it through cat...
#
set cmd [concat | $cmd |& cat]
set fd_f [open $cmd r]
fconfigure $fd_f -blocking 0 -translation binary
fileevent $fd_f readable [cb _read $fd_f $after]
}
method _read {fd after} {
set buf [read $fd]
if {$buf ne {}} {
if {![winfo exists $w.m.t]} {_init $this}
$w.m.t conf -state normal
set c 0
set n [string length $buf]
while {$c < $n} {
set cr [string first "\r" $buf $c]
set lf [string first "\n" $buf $c]
if {$cr < 0} {set cr [expr {$n + 1}]}
if {$lf < 0} {set lf [expr {$n + 1}]}
if {$lf < $cr} {
$w.m.t insert end [string range $buf $c $lf]
set console_cr [$w.m.t index {end -1c}]
set c $lf
incr c
} else {
$w.m.t delete $console_cr end
$w.m.t insert end "\n"
$w.m.t insert end [string range $buf $c $cr]
set c $cr
incr c
}
}
$w.m.t conf -state disabled
$w.m.t see end
}
fconfigure $fd -blocking 1
if {[eof $fd]} {
if {[catch {close $fd}]} {
set ok 0
} else {
set ok 1
}
if {$after ne {}} {
uplevel #0 $after $ok
} else {
done $this $ok
}
return
}
fconfigure $fd -blocking 0
}
method chain {cmdlist {ok 1}} {
if {$ok} {
if {[llength $cmdlist] == 0} {
done $this $ok
return
}
set cmd [lindex $cmdlist 0]
set cmdlist [lrange $cmdlist 1 end]
if {[lindex $cmd 0] eq {exec}} {
exec $this \
[lrange $cmd 1 end] \
[cb chain $cmdlist]
} else {
uplevel #0 $cmd [cb chain $cmdlist]
}
} else {
done $this $ok
}
}
method done {ok} {
if {$ok} {
if {[winfo exists $w.m.s]} {
$w.m.s conf -background green -text {Success}
$w.ok conf -state normal
focus $w.ok
}
} else {
if {![winfo exists $w.m.s]} {
_init $this
}
$w.m.s conf -background red -text {Error: Command Failed}
$w.ok conf -state normal
focus $w.ok
}
delete_this
}
}

89
git-gui/lib/database.tcl Normal file
View File

@@ -0,0 +1,89 @@
# git-gui object database management support
# Copyright (C) 2006, 2007 Shawn Pearce
proc do_stats {} {
set fd [open "| git count-objects -v" r]
while {[gets $fd line] > 0} {
if {[regexp {^([^:]+): (\d+)$} $line _ name value]} {
set stats($name) $value
}
}
close $fd
set packed_sz 0
foreach p [glob -directory [gitdir objects pack] \
-type f \
-nocomplain -- *] {
incr packed_sz [file size $p]
}
if {$packed_sz > 0} {
set stats(size-pack) [expr {$packed_sz / 1024}]
}
set w .stats_view
toplevel $w
wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
label $w.header -text {Database Statistics}
pack $w.header -side top -fill x
frame $w.buttons -border 1
button $w.buttons.close -text Close \
-default active \
-command [list destroy $w]
button $w.buttons.gc -text {Compress Database} \
-default normal \
-command "destroy $w;do_gc"
pack $w.buttons.close -side right
pack $w.buttons.gc -side left
pack $w.buttons -side bottom -fill x -pady 10 -padx 10
frame $w.stat -borderwidth 1 -relief solid
foreach s {
{count {Number of loose objects}}
{size {Disk space used by loose objects} { KiB}}
{in-pack {Number of packed objects}}
{packs {Number of packs}}
{size-pack {Disk space used by packed objects} { KiB}}
{prune-packable {Packed objects waiting for pruning}}
{garbage {Garbage files}}
} {
set name [lindex $s 0]
set label [lindex $s 1]
if {[catch {set value $stats($name)}]} continue
if {[llength $s] > 2} {
set value "$value[lindex $s 2]"
}
label $w.stat.l_$name -text "$label:" -anchor w
label $w.stat.v_$name -text $value -anchor w
grid $w.stat.l_$name $w.stat.v_$name -sticky we -padx {0 5}
}
pack $w.stat -pady 10 -padx 10
bind $w <Visibility> "grab $w; focus $w.buttons.close"
bind $w <Key-Escape> [list destroy $w]
bind $w <Key-Return> [list destroy $w]
wm title $w "[appname] ([reponame]): Database Statistics"
tkwait window $w
}
proc do_gc {} {
set w [console::new {gc} {Compressing the object database}]
console::chain $w {
{exec git pack-refs --prune}
{exec git reflog expire --all}
{exec git repack -a -d -l}
{exec git rerere gc}
}
}
proc do_fsck_objects {} {
set w [console::new {fsck-objects} \
{Verifying the object database with fsck-objects}]
set cmd [list git fsck-objects]
lappend cmd --full
lappend cmd --cache
lappend cmd --strict
console::exec $w $cmd
}

336
git-gui/lib/diff.tcl Normal file
View File

@@ -0,0 +1,336 @@
# git-gui diff viewer
# Copyright (C) 2006, 2007 Shawn Pearce
proc clear_diff {} {
global ui_diff current_diff_path current_diff_header
global ui_index ui_workdir
$ui_diff conf -state normal
$ui_diff delete 0.0 end
$ui_diff conf -state disabled
set current_diff_path {}
set current_diff_header {}
$ui_index tag remove in_diff 0.0 end
$ui_workdir tag remove in_diff 0.0 end
}
proc reshow_diff {} {
global ui_status_value file_states file_lists
global current_diff_path current_diff_side
set p $current_diff_path
if {$p eq {}} {
# No diff is being shown.
} elseif {$current_diff_side eq {}
|| [catch {set s $file_states($p)}]
|| [lsearch -sorted -exact $file_lists($current_diff_side) $p] == -1} {
clear_diff
} else {
show_diff $p $current_diff_side
}
}
proc handle_empty_diff {} {
global current_diff_path file_states file_lists
set path $current_diff_path
set s $file_states($path)
if {[lindex $s 0] ne {_M}} return
info_popup "No differences detected.
[short_path $path] has no changes.
The modification date of this file was updated by another application, but the content within the file was not changed.
A rescan will be automatically started to find other files which may have the same state."
clear_diff
display_file $path __
rescan {set ui_status_value {Ready.}} 0
}
proc show_diff {path w {lno {}}} {
global file_states file_lists
global is_3way_diff diff_active repo_config
global ui_diff ui_status_value ui_index ui_workdir
global current_diff_path current_diff_side current_diff_header
if {$diff_active || ![lock_index read]} return
clear_diff
if {$lno == {}} {
set lno [lsearch -sorted -exact $file_lists($w) $path]
if {$lno >= 0} {
incr lno
}
}
if {$lno >= 1} {
$w tag add in_diff $lno.0 [expr {$lno + 1}].0
}
set s $file_states($path)
set m [lindex $s 0]
set is_3way_diff 0
set diff_active 1
set current_diff_path $path
set current_diff_side $w
set current_diff_header {}
set ui_status_value "Loading diff of [escape_path $path]..."
# - Git won't give us the diff, there's nothing to compare to!
#
if {$m eq {_O}} {
set max_sz [expr {128 * 1024}]
if {[catch {
set fd [open $path r]
set content [read $fd $max_sz]
close $fd
set sz [file size $path]
} err ]} {
set diff_active 0
unlock_index
set ui_status_value "Unable to display [escape_path $path]"
error_popup "Error loading file:\n\n$err"
return
}
$ui_diff conf -state normal
if {![catch {set type [exec file $path]}]} {
set n [string length $path]
if {[string equal -length $n $path $type]} {
set type [string range $type $n end]
regsub {^:?\s*} $type {} type
}
$ui_diff insert end "* $type\n" d_@
}
if {[string first "\0" $content] != -1} {
$ui_diff insert end \
"* Binary file (not showing content)." \
d_@
} else {
if {$sz > $max_sz} {
$ui_diff insert end \
"* Untracked file is $sz bytes.
* Showing only first $max_sz bytes.
" d_@
}
$ui_diff insert end $content
if {$sz > $max_sz} {
$ui_diff insert end "
* Untracked file clipped here by [appname].
* To see the entire file, use an external editor.
" d_@
}
}
$ui_diff conf -state disabled
set diff_active 0
unlock_index
set ui_status_value {Ready.}
return
}
set cmd [list | git]
if {$w eq $ui_index} {
lappend cmd diff-index
lappend cmd --cached
} elseif {$w eq $ui_workdir} {
if {[string index $m 0] eq {U}} {
lappend cmd diff
} else {
lappend cmd diff-files
}
}
lappend cmd -p
lappend cmd --no-color
if {$repo_config(gui.diffcontext) > 0} {
lappend cmd "-U$repo_config(gui.diffcontext)"
}
if {$w eq $ui_index} {
lappend cmd [PARENT]
}
lappend cmd --
lappend cmd $path
if {[catch {set fd [open $cmd r]} err]} {
set diff_active 0
unlock_index
set ui_status_value "Unable to display [escape_path $path]"
error_popup "Error loading diff:\n\n$err"
return
}
fconfigure $fd \
-blocking 0 \
-encoding binary \
-translation binary
fileevent $fd readable [list read_diff $fd]
}
proc read_diff {fd} {
global ui_diff ui_status_value diff_active
global is_3way_diff current_diff_header
$ui_diff conf -state normal
while {[gets $fd line] >= 0} {
# -- Cleanup uninteresting diff header lines.
#
if { [string match {diff --git *} $line]
|| [string match {diff --cc *} $line]
|| [string match {diff --combined *} $line]
|| [string match {--- *} $line]
|| [string match {+++ *} $line]} {
append current_diff_header $line "\n"
continue
}
if {[string match {index *} $line]} continue
if {$line eq {deleted file mode 120000}} {
set line "deleted symlink"
}
# -- Automatically detect if this is a 3 way diff.
#
if {[string match {@@@ *} $line]} {set is_3way_diff 1}
if {[string match {mode *} $line]
|| [string match {new file *} $line]
|| [string match {deleted file *} $line]
|| [string match {Binary files * and * differ} $line]
|| $line eq {\ No newline at end of file}
|| [regexp {^\* Unmerged path } $line]} {
set tags {}
} elseif {$is_3way_diff} {
set op [string range $line 0 1]
switch -- $op {
{ } {set tags {}}
{@@} {set tags d_@}
{ +} {set tags d_s+}
{ -} {set tags d_s-}
{+ } {set tags d_+s}
{- } {set tags d_-s}
{--} {set tags d_--}
{++} {
if {[regexp {^\+\+([<>]{7} |={7})} $line _g op]} {
set line [string replace $line 0 1 { }]
set tags d$op
} else {
set tags d_++
}
}
default {
puts "error: Unhandled 3 way diff marker: {$op}"
set tags {}
}
}
} else {
set op [string index $line 0]
switch -- $op {
{ } {set tags {}}
{@} {set tags d_@}
{-} {set tags d_-}
{+} {
if {[regexp {^\+([<>]{7} |={7})} $line _g op]} {
set line [string replace $line 0 0 { }]
set tags d$op
} else {
set tags d_+
}
}
default {
puts "error: Unhandled 2 way diff marker: {$op}"
set tags {}
}
}
}
$ui_diff insert end $line $tags
if {[string index $line end] eq "\r"} {
$ui_diff tag add d_cr {end - 2c}
}
$ui_diff insert end "\n" $tags
}
$ui_diff conf -state disabled
if {[eof $fd]} {
close $fd
set diff_active 0
unlock_index
set ui_status_value {Ready.}
if {[$ui_diff index end] eq {2.0}} {
handle_empty_diff
}
}
}
proc apply_hunk {x y} {
global current_diff_path current_diff_header current_diff_side
global ui_diff ui_index file_states
if {$current_diff_path eq {} || $current_diff_header eq {}} return
if {![lock_index apply_hunk]} return
set apply_cmd {git apply --cached --whitespace=nowarn}
set mi [lindex $file_states($current_diff_path) 0]
if {$current_diff_side eq $ui_index} {
set mode unstage
lappend apply_cmd --reverse
if {[string index $mi 0] ne {M}} {
unlock_index
return
}
} else {
set mode stage
if {[string index $mi 1] ne {M}} {
unlock_index
return
}
}
set s_lno [lindex [split [$ui_diff index @$x,$y] .] 0]
set s_lno [$ui_diff search -backwards -regexp ^@@ $s_lno.0 0.0]
if {$s_lno eq {}} {
unlock_index
return
}
set e_lno [$ui_diff search -forwards -regexp ^@@ "$s_lno + 1 lines" end]
if {$e_lno eq {}} {
set e_lno end
}
if {[catch {
set p [open "| $apply_cmd" w]
fconfigure $p -translation binary -encoding binary
puts -nonewline $p $current_diff_header
puts -nonewline $p [$ui_diff get $s_lno $e_lno]
close $p} err]} {
error_popup "Failed to $mode selected hunk.\n\n$err"
unlock_index
return
}
$ui_diff conf -state normal
$ui_diff delete $s_lno $e_lno
$ui_diff conf -state disabled
if {[$ui_diff get 1.0 end] eq "\n"} {
set o _
} else {
set o ?
}
if {$current_diff_side eq $ui_index} {
set mi ${o}M
} elseif {[string index $mi 0] eq {_}} {
set mi M$o
} else {
set mi ?$o
}
unlock_index
display_file $current_diff_path $mi
if {$o eq {_}} {
clear_diff
}
}

101
git-gui/lib/error.tcl Normal file
View File

@@ -0,0 +1,101 @@
# git-gui branch (create/delete) support
# Copyright (C) 2006, 2007 Shawn Pearce
proc error_popup {msg} {
set title [appname]
if {[reponame] ne {}} {
append title " ([reponame])"
}
set cmd [list tk_messageBox \
-icon error \
-type ok \
-title "$title: error" \
-message $msg]
if {[winfo ismapped .]} {
lappend cmd -parent .
}
eval $cmd
}
proc warn_popup {msg} {
set title [appname]
if {[reponame] ne {}} {
append title " ([reponame])"
}
set cmd [list tk_messageBox \
-icon warning \
-type ok \
-title "$title: warning" \
-message $msg]
if {[winfo ismapped .]} {
lappend cmd -parent .
}
eval $cmd
}
proc info_popup {msg {parent .}} {
set title [appname]
if {[reponame] ne {}} {
append title " ([reponame])"
}
tk_messageBox \
-parent $parent \
-icon info \
-type ok \
-title $title \
-message $msg
}
proc ask_popup {msg} {
set title [appname]
if {[reponame] ne {}} {
append title " ([reponame])"
}
return [tk_messageBox \
-parent . \
-icon question \
-type yesno \
-title $title \
-message $msg]
}
proc hook_failed_popup {hook msg} {
set w .hookfail
toplevel $w
frame $w.m
label $w.m.l1 -text "$hook hook failed:" \
-anchor w \
-justify left \
-font font_uibold
text $w.m.t \
-background white -borderwidth 1 \
-relief sunken \
-width 80 -height 10 \
-font font_diff \
-yscrollcommand [list $w.m.sby set]
label $w.m.l2 \
-text {You must correct the above errors before committing.} \
-anchor w \
-justify left \
-font font_uibold
scrollbar $w.m.sby -command [list $w.m.t yview]
pack $w.m.l1 -side top -fill x
pack $w.m.l2 -side bottom -fill x
pack $w.m.sby -side right -fill y
pack $w.m.t -side left -fill both -expand 1
pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
$w.m.t insert 1.0 $msg
$w.m.t conf -state disabled
button $w.ok -text OK \
-width 15 \
-command "destroy $w"
pack $w.ok -side bottom -anchor e -pady 10 -padx 10
bind $w <Visibility> "grab $w; focus $w"
bind $w <Key-Return> "destroy $w"
wm title $w "[appname] ([reponame]): error"
tkwait window $w
}

409
git-gui/lib/index.tcl Normal file
View File

@@ -0,0 +1,409 @@
# git-gui index (add/remove) support
# Copyright (C) 2006, 2007 Shawn Pearce
proc update_indexinfo {msg pathList after} {
global update_index_cp ui_status_value
if {![lock_index update]} return
set update_index_cp 0
set pathList [lsort $pathList]
set totalCnt [llength $pathList]
set batch [expr {int($totalCnt * .01) + 1}]
if {$batch > 25} {set batch 25}
set ui_status_value [format \
"$msg... %i/%i files (%.2f%%)" \
$update_index_cp \
$totalCnt \
0.0]
set fd [open "| git update-index -z --index-info" w]
fconfigure $fd \
-blocking 0 \
-buffering full \
-buffersize 512 \
-encoding binary \
-translation binary
fileevent $fd writable [list \
write_update_indexinfo \
$fd \
$pathList \
$totalCnt \
$batch \
$msg \
$after \
]
}
proc write_update_indexinfo {fd pathList totalCnt batch msg after} {
global update_index_cp ui_status_value
global file_states current_diff_path
if {$update_index_cp >= $totalCnt} {
close $fd
unlock_index
uplevel #0 $after
return
}
for {set i $batch} \
{$update_index_cp < $totalCnt && $i > 0} \
{incr i -1} {
set path [lindex $pathList $update_index_cp]
incr update_index_cp
set s $file_states($path)
switch -glob -- [lindex $s 0] {
A? {set new _O}
M? {set new _M}
D_ {set new _D}
D? {set new _?}
?? {continue}
}
set info [lindex $s 2]
if {$info eq {}} continue
puts -nonewline $fd "$info\t[encoding convertto $path]\0"
display_file $path $new
}
set ui_status_value [format \
"$msg... %i/%i files (%.2f%%)" \
$update_index_cp \
$totalCnt \
[expr {100.0 * $update_index_cp / $totalCnt}]]
}
proc update_index {msg pathList after} {
global update_index_cp ui_status_value
if {![lock_index update]} return
set update_index_cp 0
set pathList [lsort $pathList]
set totalCnt [llength $pathList]
set batch [expr {int($totalCnt * .01) + 1}]
if {$batch > 25} {set batch 25}
set ui_status_value [format \
"$msg... %i/%i files (%.2f%%)" \
$update_index_cp \
$totalCnt \
0.0]
set fd [open "| git update-index --add --remove -z --stdin" w]
fconfigure $fd \
-blocking 0 \
-buffering full \
-buffersize 512 \
-encoding binary \
-translation binary
fileevent $fd writable [list \
write_update_index \
$fd \
$pathList \
$totalCnt \
$batch \
$msg \
$after \
]
}
proc write_update_index {fd pathList totalCnt batch msg after} {
global update_index_cp ui_status_value
global file_states current_diff_path
if {$update_index_cp >= $totalCnt} {
close $fd
unlock_index
uplevel #0 $after
return
}
for {set i $batch} \
{$update_index_cp < $totalCnt && $i > 0} \
{incr i -1} {
set path [lindex $pathList $update_index_cp]
incr update_index_cp
switch -glob -- [lindex $file_states($path) 0] {
AD {set new __}
?D {set new D_}
_O -
AM {set new A_}
U? {
if {[file exists $path]} {
set new M_
} else {
set new D_
}
}
?M {set new M_}
?? {continue}
}
puts -nonewline $fd "[encoding convertto $path]\0"
display_file $path $new
}
set ui_status_value [format \
"$msg... %i/%i files (%.2f%%)" \
$update_index_cp \
$totalCnt \
[expr {100.0 * $update_index_cp / $totalCnt}]]
}
proc checkout_index {msg pathList after} {
global update_index_cp ui_status_value
if {![lock_index update]} return
set update_index_cp 0
set pathList [lsort $pathList]
set totalCnt [llength $pathList]
set batch [expr {int($totalCnt * .01) + 1}]
if {$batch > 25} {set batch 25}
set ui_status_value [format \
"$msg... %i/%i files (%.2f%%)" \
$update_index_cp \
$totalCnt \
0.0]
set cmd [list git checkout-index]
lappend cmd --index
lappend cmd --quiet
lappend cmd --force
lappend cmd -z
lappend cmd --stdin
set fd [open "| $cmd " w]
fconfigure $fd \
-blocking 0 \
-buffering full \
-buffersize 512 \
-encoding binary \
-translation binary
fileevent $fd writable [list \
write_checkout_index \
$fd \
$pathList \
$totalCnt \
$batch \
$msg \
$after \
]
}
proc write_checkout_index {fd pathList totalCnt batch msg after} {
global update_index_cp ui_status_value
global file_states current_diff_path
if {$update_index_cp >= $totalCnt} {
close $fd
unlock_index
uplevel #0 $after
return
}
for {set i $batch} \
{$update_index_cp < $totalCnt && $i > 0} \
{incr i -1} {
set path [lindex $pathList $update_index_cp]
incr update_index_cp
switch -glob -- [lindex $file_states($path) 0] {
U? {continue}
?M -
?D {
puts -nonewline $fd "[encoding convertto $path]\0"
display_file $path ?_
}
}
}
set ui_status_value [format \
"$msg... %i/%i files (%.2f%%)" \
$update_index_cp \
$totalCnt \
[expr {100.0 * $update_index_cp / $totalCnt}]]
}
proc unstage_helper {txt paths} {
global file_states current_diff_path
if {![lock_index begin-update]} return
set pathList [list]
set after {}
foreach path $paths {
switch -glob -- [lindex $file_states($path) 0] {
A? -
M? -
D? {
lappend pathList $path
if {$path eq $current_diff_path} {
set after {reshow_diff;}
}
}
}
}
if {$pathList eq {}} {
unlock_index
} else {
update_indexinfo \
$txt \
$pathList \
[concat $after {set ui_status_value {Ready.}}]
}
}
proc do_unstage_selection {} {
global current_diff_path selected_paths
if {[array size selected_paths] > 0} {
unstage_helper \
{Unstaging selected files from commit} \
[array names selected_paths]
} elseif {$current_diff_path ne {}} {
unstage_helper \
"Unstaging [short_path $current_diff_path] from commit" \
[list $current_diff_path]
}
}
proc add_helper {txt paths} {
global file_states current_diff_path
if {![lock_index begin-update]} return
set pathList [list]
set after {}
foreach path $paths {
switch -glob -- [lindex $file_states($path) 0] {
_O -
?M -
?D -
U? {
lappend pathList $path
if {$path eq $current_diff_path} {
set after {reshow_diff;}
}
}
}
}
if {$pathList eq {}} {
unlock_index
} else {
update_index \
$txt \
$pathList \
[concat $after {set ui_status_value {Ready to commit.}}]
}
}
proc do_add_selection {} {
global current_diff_path selected_paths
if {[array size selected_paths] > 0} {
add_helper \
{Adding selected files} \
[array names selected_paths]
} elseif {$current_diff_path ne {}} {
add_helper \
"Adding [short_path $current_diff_path]" \
[list $current_diff_path]
}
}
proc do_add_all {} {
global file_states
set paths [list]
foreach path [array names file_states] {
switch -glob -- [lindex $file_states($path) 0] {
U? {continue}
?M -
?D {lappend paths $path}
}
}
add_helper {Adding all changed files} $paths
}
proc revert_helper {txt paths} {
global file_states current_diff_path
if {![lock_index begin-update]} return
set pathList [list]
set after {}
foreach path $paths {
switch -glob -- [lindex $file_states($path) 0] {
U? {continue}
?M -
?D {
lappend pathList $path
if {$path eq $current_diff_path} {
set after {reshow_diff;}
}
}
}
}
set n [llength $pathList]
if {$n == 0} {
unlock_index
return
} elseif {$n == 1} {
set s "[short_path [lindex $pathList]]"
} else {
set s "these $n files"
}
set reply [tk_dialog \
.confirm_revert \
"[appname] ([reponame])" \
"Revert changes in $s?
Any unadded changes will be permanently lost by the revert." \
question \
1 \
{Do Nothing} \
{Revert Changes} \
]
if {$reply == 1} {
checkout_index \
$txt \
$pathList \
[concat $after {set ui_status_value {Ready.}}]
} else {
unlock_index
}
}
proc do_revert_selection {} {
global current_diff_path selected_paths
if {[array size selected_paths] > 0} {
revert_helper \
{Reverting selected files} \
[array names selected_paths]
} elseif {$current_diff_path ne {}} {
revert_helper \
"Reverting [short_path $current_diff_path]" \
[list $current_diff_path]
}
}
proc do_select_commit_type {} {
global commit_type selected_commit_type
if {$selected_commit_type eq {new}
&& [string match amend* $commit_type]} {
create_new_commit
} elseif {$selected_commit_type eq {amend}
&& ![string match amend* $commit_type]} {
load_last_commit
# The amend request was rejected...
#
if {![string match amend* $commit_type]} {
set selected_commit_type new
}
}
}

312
git-gui/lib/merge.tcl Normal file
View File

@@ -0,0 +1,312 @@
# git-gui branch merge support
# Copyright (C) 2006, 2007 Shawn Pearce
namespace eval merge {
proc _can_merge {} {
global HEAD commit_type file_states
if {[string match amend* $commit_type]} {
info_popup {Cannot merge while amending.
You must finish amending this commit before starting any type of merge.
}
return 0
}
if {[committer_ident] eq {}} {return 0}
if {![lock_index merge]} {return 0}
# -- Our in memory state should match the repository.
#
repository_state curType curHEAD curMERGE_HEAD
if {$commit_type ne $curType || $HEAD ne $curHEAD} {
info_popup {Last scanned state does not match repository state.
Another Git program has modified this repository since the last scan. A rescan must be performed before a merge can be performed.
The rescan will be automatically started now.
}
unlock_index
rescan {set ui_status_value {Ready.}}
return 0
}
foreach path [array names file_states] {
switch -glob -- [lindex $file_states($path) 0] {
_O {
continue; # and pray it works!
}
U? {
error_popup "You are in the middle of a conflicted merge.
File [short_path $path] has merge conflicts.
You must resolve them, add the file, and commit to complete the current merge. Only then can you begin another merge.
"
unlock_index
return 0
}
?? {
error_popup "You are in the middle of a change.
File [short_path $path] is modified.
You should complete the current commit before starting a merge. Doing so will help you abort a failed merge, should the need arise.
"
unlock_index
return 0
}
}
}
return 1
}
proc _refs {w list} {
set r {}
foreach i [$w.source.l curselection] {
lappend r [lindex [lindex $list $i] 0]
}
return $r
}
proc _visualize {w list} {
set revs [_refs $w $list]
if {$revs eq {}} return
lappend revs --not HEAD
do_gitk $revs
}
proc _start {w list} {
global HEAD ui_status_value current_branch
set cmd [list git merge]
set names [_refs $w $list]
set revcnt [llength $names]
append cmd { } $names
if {$revcnt == 0} {
return
} elseif {$revcnt == 1} {
set unit branch
} elseif {$revcnt <= 15} {
set unit branches
if {[tk_dialog \
$w.confirm_octopus \
[wm title $w] \
"Use octopus merge strategy?
You are merging $revcnt branches at once. This requires using the octopus merge driver, which may not succeed if there are file-level conflicts.
" \
question \
0 \
{Cancel} \
{Use octopus} \
] != 1} return
} else {
tk_messageBox \
-icon error \
-type ok \
-title [wm title $w] \
-parent $w \
-message "Too many branches selected.
You have requested to merge $revcnt branches in an octopus merge. This exceeds Git's internal limit of 15 branches per merge.
Please select fewer branches. To merge more than 15 branches, merge the branches in batches.
"
return
}
set msg "Merging $current_branch, [join $names {, }]"
set ui_status_value "$msg..."
set cons [console::new "Merge" $msg]
console::exec $cons $cmd \
[namespace code [list _finish $revcnt $cons]]
bind $w <Destroy> {}
destroy $w
}
proc _finish {revcnt w ok} {
console::done $w $ok
if {$ok} {
set msg {Merge completed successfully.}
} else {
if {$revcnt != 1} {
info_popup "Octopus merge failed.
Your merge of $revcnt branches has failed.
There are file-level conflicts between the branches which must be resolved manually.
The working directory will now be reset.
You can attempt this merge again by merging only one branch at a time." $w
set fd [open "| git read-tree --reset -u HEAD" r]
fconfigure $fd -blocking 0 -translation binary
fileevent $fd readable \
[namespace code [list _reset_wait $fd]]
set ui_status_value {Aborting... please wait...}
return
}
set msg {Merge failed. Conflict resolution is required.}
}
unlock_index
rescan [list set ui_status_value $msg]
}
proc dialog {} {
global current_branch
global M1B
if {![_can_merge]} return
set fmt {list %(objectname) %(*objectname) %(refname) %(subject)}
set cmd [list git for-each-ref --tcl --format=$fmt]
lappend cmd refs/heads
lappend cmd refs/remotes
lappend cmd refs/tags
set fr_fd [open "| $cmd" r]
fconfigure $fr_fd -translation binary
while {[gets $fr_fd line] > 0} {
set line [eval $line]
set ref [lindex $line 2]
regsub ^refs/(heads|remotes|tags)/ $ref {} ref
set subj($ref) [lindex $line 3]
lappend sha1([lindex $line 0]) $ref
if {[lindex $line 1] ne {}} {
lappend sha1([lindex $line 1]) $ref
}
}
close $fr_fd
set to_show {}
set fr_fd [open "| git rev-list --all --not HEAD"]
while {[gets $fr_fd line] > 0} {
if {[catch {set ref $sha1($line)}]} continue
foreach n $ref {
lappend to_show [list $n $line]
}
}
close $fr_fd
set to_show [lsort -unique $to_show]
set w .merge_setup
toplevel $w
wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
set _visualize [namespace code [list _visualize $w $to_show]]
set _start [namespace code [list _start $w $to_show]]
label $w.header \
-text "Merge Into $current_branch" \
-font font_uibold
pack $w.header -side top -fill x
frame $w.buttons
button $w.buttons.visualize -text Visualize -command $_visualize
pack $w.buttons.visualize -side left
button $w.buttons.create -text Merge -command $_start
pack $w.buttons.create -side right
button $w.buttons.cancel -text {Cancel} -command [list destroy $w]
pack $w.buttons.cancel -side right -padx 5
pack $w.buttons -side bottom -fill x -pady 10 -padx 10
labelframe $w.source -text {Source Branches}
listbox $w.source.l \
-height 10 \
-width 70 \
-font font_diff \
-selectmode extended \
-yscrollcommand [list $w.source.sby set]
scrollbar $w.source.sby -command [list $w.source.l yview]
pack $w.source.sby -side right -fill y
pack $w.source.l -side left -fill both -expand 1
pack $w.source -fill both -expand 1 -pady 5 -padx 5
foreach ref $to_show {
set n [lindex $ref 0]
if {[string length $n] > 20} {
set n "[string range $n 0 16]..."
}
$w.source.l insert end [format {%s %-20s %s} \
[string range [lindex $ref 1] 0 5] \
$n \
$subj([lindex $ref 0])]
}
bind $w.source.l <Key-K> [list event generate %W <Shift-Key-Up>]
bind $w.source.l <Key-J> [list event generate %W <Shift-Key-Down>]
bind $w.source.l <Key-k> [list event generate %W <Key-Up>]
bind $w.source.l <Key-j> [list event generate %W <Key-Down>]
bind $w.source.l <Key-h> [list event generate %W <Key-Left>]
bind $w.source.l <Key-l> [list event generate %W <Key-Right>]
bind $w.source.l <Key-v> $_visualize
bind $w <$M1B-Key-Return> $_start
bind $w <Visibility> "grab $w; focus $w.source.l"
bind $w <Key-Escape> "unlock_index;destroy $w"
bind $w <Destroy> unlock_index
wm title $w "[appname] ([reponame]): Merge"
tkwait window $w
}
proc reset_hard {} {
global HEAD commit_type file_states
if {[string match amend* $commit_type]} {
info_popup {Cannot abort while amending.
You must finish amending this commit.
}
return
}
if {![lock_index abort]} return
if {[string match *merge* $commit_type]} {
set op merge
} else {
set op commit
}
if {[ask_popup "Abort $op?
Aborting the current $op will cause *ALL* uncommitted changes to be lost.
Continue with aborting the current $op?"] eq {yes}} {
set fd [open "| git read-tree --reset -u HEAD" r]
fconfigure $fd -blocking 0 -translation binary
fileevent $fd readable [namespace code [list _reset_wait $fd]]
set ui_status_value {Aborting... please wait...}
} else {
unlock_index
}
}
proc _reset_wait {fd} {
global ui_comm
read $fd
if {[eof $fd]} {
close $fd
unlock_index
$ui_comm delete 0.0 end
$ui_comm edit modified false
catch {file delete [gitdir MERGE_HEAD]}
catch {file delete [gitdir rr-cache MERGE_RR]}
catch {file delete [gitdir SQUASH_MSG]}
catch {file delete [gitdir MERGE_MSG]}
catch {file delete [gitdir GITGUI_MSG]}
rescan {set ui_status_value {Abort completed. Ready.}}
}
}
}

290
git-gui/lib/option.tcl Normal file
View File

@@ -0,0 +1,290 @@
# git-gui options editor
# Copyright (C) 2006, 2007 Shawn Pearce
proc save_config {} {
global default_config font_descs
global repo_config global_config
global repo_config_new global_config_new
foreach option $font_descs {
set name [lindex $option 0]
set font [lindex $option 1]
font configure $font \
-family $global_config_new(gui.$font^^family) \
-size $global_config_new(gui.$font^^size)
font configure ${font}bold \
-family $global_config_new(gui.$font^^family) \
-size $global_config_new(gui.$font^^size)
set global_config_new(gui.$name) [font configure $font]
unset global_config_new(gui.$font^^family)
unset global_config_new(gui.$font^^size)
}
foreach name [array names default_config] {
set value $global_config_new($name)
if {$value ne $global_config($name)} {
if {$value eq $default_config($name)} {
catch {git config --global --unset $name}
} else {
regsub -all "\[{}\]" $value {"} value
git config --global $name $value
}
set global_config($name) $value
if {$value eq $repo_config($name)} {
catch {git config --unset $name}
set repo_config($name) $value
}
}
}
foreach name [array names default_config] {
set value $repo_config_new($name)
if {$value ne $repo_config($name)} {
if {$value eq $global_config($name)} {
catch {git config --unset $name}
} else {
regsub -all "\[{}\]" $value {"} value
git config $name $value
}
set repo_config($name) $value
}
}
}
proc do_about {} {
global appvers copyright
global tcl_patchLevel tk_patchLevel
set w .about_dialog
toplevel $w
wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
label $w.header -text "About [appname]" \
-font font_uibold
pack $w.header -side top -fill x
frame $w.buttons
button $w.buttons.close -text {Close} \
-default active \
-command [list destroy $w]
pack $w.buttons.close -side right
pack $w.buttons -side bottom -fill x -pady 10 -padx 10
label $w.desc \
-text "git-gui - a graphical user interface for Git.
$copyright" \
-padx 5 -pady 5 \
-justify left \
-anchor w \
-borderwidth 1 \
-relief solid
pack $w.desc -side top -fill x -padx 5 -pady 5
set v {}
append v "git-gui version $appvers\n"
append v "[git version]\n"
append v "\n"
if {$tcl_patchLevel eq $tk_patchLevel} {
append v "Tcl/Tk version $tcl_patchLevel"
} else {
append v "Tcl version $tcl_patchLevel"
append v ", Tk version $tk_patchLevel"
}
label $w.vers \
-text $v \
-padx 5 -pady 5 \
-justify left \
-anchor w \
-borderwidth 1 \
-relief solid
pack $w.vers -side top -fill x -padx 5 -pady 5
menu $w.ctxm -tearoff 0
$w.ctxm add command \
-label {Copy} \
-command "
clipboard clear
clipboard append -format STRING -type STRING -- \[$w.vers cget -text\]
"
bind $w <Visibility> "grab $w; focus $w.buttons.close"
bind $w <Key-Escape> "destroy $w"
bind $w <Key-Return> "destroy $w"
bind_button3 $w.vers "tk_popup $w.ctxm %X %Y; grab $w; focus $w"
wm title $w "About [appname]"
tkwait window $w
}
proc do_options {} {
global repo_config global_config font_descs
global repo_config_new global_config_new
array unset repo_config_new
array unset global_config_new
foreach name [array names repo_config] {
set repo_config_new($name) $repo_config($name)
}
load_config 1
foreach name [array names repo_config] {
switch -- $name {
gui.diffcontext {continue}
}
set repo_config_new($name) $repo_config($name)
}
foreach name [array names global_config] {
set global_config_new($name) $global_config($name)
}
set w .options_editor
toplevel $w
wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
label $w.header -text "Options" \
-font font_uibold
pack $w.header -side top -fill x
frame $w.buttons
button $w.buttons.restore -text {Restore Defaults} \
-default normal \
-command do_restore_defaults
pack $w.buttons.restore -side left
button $w.buttons.save -text Save \
-default active \
-command [list do_save_config $w]
pack $w.buttons.save -side right
button $w.buttons.cancel -text {Cancel} \
-default normal \
-command [list destroy $w]
pack $w.buttons.cancel -side right -padx 5
pack $w.buttons -side bottom -fill x -pady 10 -padx 10
labelframe $w.repo -text "[reponame] Repository"
labelframe $w.global -text {Global (All Repositories)}
pack $w.repo -side left -fill both -expand 1 -pady 5 -padx 5
pack $w.global -side right -fill both -expand 1 -pady 5 -padx 5
set optid 0
foreach option {
{t user.name {User Name}}
{t user.email {Email Address}}
{b merge.summary {Summarize Merge Commits}}
{i-1..5 merge.verbosity {Merge Verbosity}}
{b gui.trustmtime {Trust File Modification Timestamps}}
{i-1..99 gui.diffcontext {Number of Diff Context Lines}}
{t gui.newbranchtemplate {New Branch Name Template}}
} {
set type [lindex $option 0]
set name [lindex $option 1]
set text [lindex $option 2]
incr optid
foreach f {repo global} {
switch -glob -- $type {
b {
checkbutton $w.$f.$optid -text $text \
-variable ${f}_config_new($name) \
-onvalue true \
-offvalue false
pack $w.$f.$optid -side top -anchor w
}
i-* {
regexp -- {-(\d+)\.\.(\d+)$} $type _junk min max
frame $w.$f.$optid
label $w.$f.$optid.l -text "$text:"
pack $w.$f.$optid.l -side left -anchor w -fill x
spinbox $w.$f.$optid.v \
-textvariable ${f}_config_new($name) \
-from $min \
-to $max \
-increment 1 \
-width [expr {1 + [string length $max]}]
bind $w.$f.$optid.v <FocusIn> {%W selection range 0 end}
pack $w.$f.$optid.v -side right -anchor e -padx 5
pack $w.$f.$optid -side top -anchor w -fill x
}
t {
frame $w.$f.$optid
label $w.$f.$optid.l -text "$text:"
entry $w.$f.$optid.v \
-borderwidth 1 \
-relief sunken \
-width 20 \
-textvariable ${f}_config_new($name)
pack $w.$f.$optid.l -side left -anchor w
pack $w.$f.$optid.v -side left -anchor w \
-fill x -expand 1 \
-padx 5
pack $w.$f.$optid -side top -anchor w -fill x
}
}
}
}
set all_fonts [lsort [font families]]
foreach option $font_descs {
set name [lindex $option 0]
set font [lindex $option 1]
set text [lindex $option 2]
set global_config_new(gui.$font^^family) \
[font configure $font -family]
set global_config_new(gui.$font^^size) \
[font configure $font -size]
frame $w.global.$name
label $w.global.$name.l -text "$text:"
pack $w.global.$name.l -side left -anchor w -fill x
eval tk_optionMenu $w.global.$name.family \
global_config_new(gui.$font^^family) \
$all_fonts
spinbox $w.global.$name.size \
-textvariable global_config_new(gui.$font^^size) \
-from 2 -to 80 -increment 1 \
-width 3
bind $w.global.$name.size <FocusIn> {%W selection range 0 end}
pack $w.global.$name.size -side right -anchor e
pack $w.global.$name.family -side right -anchor e
pack $w.global.$name -side top -anchor w -fill x
}
bind $w <Visibility> "grab $w; focus $w.buttons.save"
bind $w <Key-Escape> "destroy $w"
bind $w <Key-Return> [list do_save_config $w]
wm title $w "[appname] ([reponame]): Options"
tkwait window $w
}
proc do_restore_defaults {} {
global font_descs default_config repo_config
global repo_config_new global_config_new
foreach name [array names default_config] {
set repo_config_new($name) $default_config($name)
set global_config_new($name) $default_config($name)
}
foreach option $font_descs {
set name [lindex $option 0]
set repo_config(gui.$name) $default_config(gui.$name)
}
apply_config
foreach option $font_descs {
set name [lindex $option 0]
set font [lindex $option 1]
set global_config_new(gui.$font^^family) \
[font configure $font -family]
set global_config_new(gui.$font^^size) \
[font configure $font -size]
}
}
proc do_save_config {w} {
if {[catch {save_config} err]} {
error_popup "Failed to completely save options:\n\n$err"
}
reshow_diff
destroy $w
}

159
git-gui/lib/remote.tcl Normal file
View File

@@ -0,0 +1,159 @@
# git-gui remote management
# Copyright (C) 2006, 2007 Shawn Pearce
proc is_tracking_branch {name} {
global tracking_branches
if {![catch {set info $tracking_branches($name)}]} {
return 1
}
foreach t [array names tracking_branches] {
if {[string match {*/\*} $t] && [string match $t $name]} {
return 1
}
}
return 0
}
proc all_tracking_branches {} {
global tracking_branches
set all_trackings {}
set cmd {}
foreach name [array names tracking_branches] {
if {[regsub {/\*$} $name {} name]} {
lappend cmd $name
} else {
regsub ^refs/(heads|remotes)/ $name {} name
lappend all_trackings $name
}
}
if {$cmd ne {}} {
set fd [open "| git for-each-ref --format=%(refname) $cmd" r]
while {[gets $fd name] > 0} {
regsub ^refs/(heads|remotes)/ $name {} name
lappend all_trackings $name
}
close $fd
}
return [lsort -unique $all_trackings]
}
proc load_all_remotes {} {
global repo_config
global all_remotes tracking_branches
set all_remotes [list]
array unset tracking_branches
set rm_dir [gitdir remotes]
if {[file isdirectory $rm_dir]} {
set all_remotes [glob \
-types f \
-tails \
-nocomplain \
-directory $rm_dir *]
foreach name $all_remotes {
catch {
set fd [open [file join $rm_dir $name] r]
while {[gets $fd line] >= 0} {
if {![regexp {^Pull:[ ]*([^:]+):(.+)$} \
$line line src dst]} continue
if {![regexp ^refs/ $dst]} {
set dst "refs/heads/$dst"
}
set tracking_branches($dst) [list $name $src]
}
close $fd
}
}
}
foreach line [array names repo_config remote.*.url] {
if {![regexp ^remote\.(.*)\.url\$ $line line name]} continue
lappend all_remotes $name
if {[catch {set fl $repo_config(remote.$name.fetch)}]} {
set fl {}
}
foreach line $fl {
if {![regexp {^([^:]+):(.+)$} $line line src dst]} continue
if {![regexp ^refs/ $dst]} {
set dst "refs/heads/$dst"
}
set tracking_branches($dst) [list $name $src]
}
}
set all_remotes [lsort -unique $all_remotes]
}
proc populate_fetch_menu {} {
global all_remotes repo_config
set m .mbar.fetch
foreach r $all_remotes {
set enable 0
if {![catch {set a $repo_config(remote.$r.url)}]} {
if {![catch {set a $repo_config(remote.$r.fetch)}]} {
set enable 1
}
} else {
catch {
set fd [open [gitdir remotes $r] r]
while {[gets $fd n] >= 0} {
if {[regexp {^Pull:[ \t]*([^:]+):} $n]} {
set enable 1
break
}
}
close $fd
}
}
if {$enable} {
$m add command \
-label "Fetch from $r..." \
-command [list fetch_from $r]
}
}
}
proc populate_push_menu {} {
global all_remotes repo_config
set m .mbar.push
set fast_count 0
foreach r $all_remotes {
set enable 0
if {![catch {set a $repo_config(remote.$r.url)}]} {
if {![catch {set a $repo_config(remote.$r.push)}]} {
set enable 1
}
} else {
catch {
set fd [open [gitdir remotes $r] r]
while {[gets $fd n] >= 0} {
if {[regexp {^Push:[ \t]*([^:]+):} $n]} {
set enable 1
break
}
}
close $fd
}
}
if {$enable} {
if {!$fast_count} {
$m add separator
}
$m add command \
-label "Push to $r..." \
-command [list push_to $r]
incr fast_count
}
}
}

141
git-gui/lib/shortcut.tcl Normal file
View File

@@ -0,0 +1,141 @@
# git-gui desktop icon creators
# Copyright (C) 2006, 2007 Shawn Pearce
proc do_windows_shortcut {} {
global argv0
set fn [tk_getSaveFile \
-parent . \
-title "[appname] ([reponame]): Create Desktop Icon" \
-initialfile "Git [reponame].bat"]
if {$fn != {}} {
if {[catch {
set fd [open $fn w]
puts $fd "@ECHO Entering [reponame]"
puts $fd "@ECHO Starting git-gui... please wait..."
puts $fd "@SET PATH=[file normalize [gitexec]];%PATH%"
puts $fd "@SET GIT_DIR=[file normalize [gitdir]]"
puts -nonewline $fd "@\"[info nameofexecutable]\""
puts $fd " \"[file normalize $argv0]\""
close $fd
} err]} {
error_popup "Cannot write script:\n\n$err"
}
}
}
proc do_cygwin_shortcut {} {
global argv0
if {[catch {
set desktop [exec cygpath \
--windows \
--absolute \
--long-name \
--desktop]
}]} {
set desktop .
}
set fn [tk_getSaveFile \
-parent . \
-title "[appname] ([reponame]): Create Desktop Icon" \
-initialdir $desktop \
-initialfile "Git [reponame].bat"]
if {$fn != {}} {
if {[catch {
set fd [open $fn w]
set sh [exec cygpath \
--windows \
--absolute \
/bin/sh]
set me [exec cygpath \
--unix \
--absolute \
$argv0]
set gd [exec cygpath \
--unix \
--absolute \
[gitdir]]
set gw [exec cygpath \
--windows \
--absolute \
[file dirname [gitdir]]]
regsub -all ' $me "'\\''" me
regsub -all ' $gd "'\\''" gd
puts $fd "@ECHO Entering $gw"
puts $fd "@ECHO Starting git-gui... please wait..."
puts -nonewline $fd "@\"$sh\" --login -c \""
puts -nonewline $fd "GIT_DIR='$gd'"
puts -nonewline $fd " '$me'"
puts $fd "&\""
close $fd
} err]} {
error_popup "Cannot write script:\n\n$err"
}
}
}
proc do_macosx_app {} {
global argv0 env
set fn [tk_getSaveFile \
-parent . \
-title "[appname] ([reponame]): Create Desktop Icon" \
-initialdir [file join $env(HOME) Desktop] \
-initialfile "Git [reponame].app"]
if {$fn != {}} {
if {[catch {
set Contents [file join $fn Contents]
set MacOS [file join $Contents MacOS]
set exe [file join $MacOS git-gui]
file mkdir $MacOS
set fd [open [file join $Contents Info.plist] w]
puts $fd {<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>git-gui</string>
<key>CFBundleIdentifier</key>
<string>org.spearce.git-gui</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
</plist>}
close $fd
set fd [open $exe w]
set gd [file normalize [gitdir]]
set ep [file normalize [gitexec]]
regsub -all ' $gd "'\\''" gd
regsub -all ' $ep "'\\''" ep
puts $fd "#!/bin/sh"
foreach name [array names env] {
if {[string match GIT_* $name]} {
regsub -all ' $env($name) "'\\''" v
puts $fd "export $name='$v'"
}
}
puts $fd "export PATH='$ep':\$PATH"
puts $fd "export GIT_DIR='$gd'"
puts $fd "exec [file normalize $argv0]"
close $fd
file attributes $exe -permissions u+x,g+x,o+x
} err]} {
error_popup "Cannot write icon:\n\n$err"
}
}
}

164
git-gui/lib/transport.tcl Normal file
View File

@@ -0,0 +1,164 @@
# git-gui transport (fetch/push) support
# Copyright (C) 2006, 2007 Shawn Pearce
proc fetch_from {remote} {
set w [console::new \
"fetch $remote" \
"Fetching new changes from $remote"]
set cmd [list git fetch]
lappend cmd $remote
console::exec $w $cmd
}
proc push_to {remote} {
set w [console::new \
"push $remote" \
"Pushing changes to $remote"]
set cmd [list git push]
lappend cmd -v
lappend cmd $remote
console::exec $w $cmd
}
proc start_push_anywhere_action {w} {
global push_urltype push_remote push_url push_thin push_tags
set r_url {}
switch -- $push_urltype {
remote {set r_url $push_remote}
url {set r_url $push_url}
}
if {$r_url eq {}} return
set cmd [list git push]
lappend cmd -v
if {$push_thin} {
lappend cmd --thin
}
if {$push_tags} {
lappend cmd --tags
}
lappend cmd $r_url
set cnt 0
foreach i [$w.source.l curselection] {
set b [$w.source.l get $i]
lappend cmd "refs/heads/$b:refs/heads/$b"
incr cnt
}
if {$cnt == 0} {
return
} elseif {$cnt == 1} {
set unit branch
} else {
set unit branches
}
set cons [console::new \
"push $r_url" \
"Pushing $cnt $unit to $r_url"]
console::exec $cons $cmd
destroy $w
}
trace add variable push_remote write \
[list radio_selector push_urltype remote]
proc do_push_anywhere {} {
global all_heads all_remotes current_branch
global push_urltype push_remote push_url push_thin push_tags
set w .push_setup
toplevel $w
wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
label $w.header -text {Push Branches} -font font_uibold
pack $w.header -side top -fill x
frame $w.buttons
button $w.buttons.create -text Push \
-default active \
-command [list start_push_anywhere_action $w]
pack $w.buttons.create -side right
button $w.buttons.cancel -text {Cancel} \
-default normal \
-command [list destroy $w]
pack $w.buttons.cancel -side right -padx 5
pack $w.buttons -side bottom -fill x -pady 10 -padx 10
labelframe $w.source -text {Source Branches}
listbox $w.source.l \
-height 10 \
-width 70 \
-selectmode extended \
-yscrollcommand [list $w.source.sby set]
foreach h $all_heads {
$w.source.l insert end $h
if {$h eq $current_branch} {
$w.source.l select set end
}
}
scrollbar $w.source.sby -command [list $w.source.l yview]
pack $w.source.sby -side right -fill y
pack $w.source.l -side left -fill both -expand 1
pack $w.source -fill both -expand 1 -pady 5 -padx 5
labelframe $w.dest -text {Destination Repository}
if {$all_remotes ne {}} {
radiobutton $w.dest.remote_r \
-text {Remote:} \
-value remote \
-variable push_urltype
eval tk_optionMenu $w.dest.remote_m push_remote $all_remotes
grid $w.dest.remote_r $w.dest.remote_m -sticky w
if {[lsearch -sorted -exact $all_remotes origin] != -1} {
set push_remote origin
} else {
set push_remote [lindex $all_remotes 0]
}
set push_urltype remote
} else {
set push_urltype url
}
radiobutton $w.dest.url_r \
-text {Arbitrary URL:} \
-value url \
-variable push_urltype
entry $w.dest.url_t \
-borderwidth 1 \
-relief sunken \
-width 50 \
-textvariable push_url \
-validate key \
-validatecommand {
if {%d == 1 && [regexp {\s} %S]} {return 0}
if {%d == 1 && [string length %S] > 0} {
set push_urltype url
}
return 1
}
grid $w.dest.url_r $w.dest.url_t -sticky we -padx {0 5}
grid columnconfigure $w.dest 1 -weight 1
pack $w.dest -anchor nw -fill x -pady 5 -padx 5
labelframe $w.options -text {Transfer Options}
checkbutton $w.options.thin \
-text {Use thin pack (for slow network connections)} \
-variable push_thin
grid $w.options.thin -columnspan 2 -sticky w
checkbutton $w.options.tags \
-text {Include tags} \
-variable push_tags
grid $w.options.tags -columnspan 2 -sticky w
grid columnconfigure $w.options 1 -weight 1
pack $w.options -anchor nw -fill x -pady 5 -padx 5
set push_url {}
set push_thin 0
set push_tags 0
bind $w <Visibility> "grab $w; focus $w.buttons.create"
bind $w <Key-Escape> "destroy $w"
bind $w <Key-Return> [list start_push_anywhere_action $w]
wm title $w "[appname] ([reponame]): Push"
tkwait window $w
}

View File

@@ -297,9 +297,9 @@ sub update_remote {
} elsif ($name eq 'default') {
undef @remotes;
for (sort keys %$remote) {
my $do_fetch = $git->config_boolean("remote." . $_ .
my $do_fetch = $git->config_bool("remote." . $_ .
".skipDefaultUpdate");
if (!defined($do_fetch) || $do_fetch ne "true") {
unless ($do_fetch) {
push @remotes, $_;
}
}

View File

@@ -154,8 +154,8 @@ if ($@) {
$term = new FakeTerm "$@: going non-interactive";
}
my $def_chain = $repo->config_boolean('sendemail.chainreplyto');
if ($def_chain and $def_chain eq 'false') {
my $def_chain = $repo->config_bool('sendemail.chainreplyto');
if (defined $def_chain and not $def_chain) {
$chain_reply_to = 0;
}

View File

@@ -163,6 +163,7 @@ rm -rf $RPM_BUILD_ROOT
%defattr(-,root,root)
%{_bindir}/git-gui
%{_bindir}/git-citool
%{_datadir}/git-gui/
# Not Yet...
# %{!?_without_docs: %{_mandir}/man1/git-gui.1}
# %{!?_without_docs: %doc Documentation/git-gui.html}
@@ -183,9 +184,12 @@ rm -rf $RPM_BUILD_ROOT
%defattr(-,root,root)
%{_datadir}/git-core/
%doc README COPYING Documentation/*.txt
%{!?_without_docs: %doc Documentation/*.html }
%{!?_without_docs: %doc Documentation/*.html Documentation/howto}
%changelog
* Tue May 8 2007 Quy Tonthat <qtonthat@gmail.com>
- Added howto files
* Tue Mar 27 2007 Eygene Ryabinkin <rea-git@codelabs.ru>
- Added the git-p4 package: Perforce import stuff.

17
gitk
View File

@@ -593,6 +593,7 @@ proc makewindow {} {
frame .bleft -width $geometry(botwidth) -height $geometry(botheight)
}
frame .bleft.top
frame .bleft.mid
button .bleft.top.search -text "Search" -command dosearch \
-font $uifont
@@ -602,12 +603,20 @@ proc makewindow {} {
lappend entries $sstring
trace add variable searchstring write incrsearch
pack $sstring -side left -expand 1 -fill x
radiobutton .bleft.mid.diff -text "Diff" \
-command changediffdisp -variable diffelide -value {0 0}
radiobutton .bleft.mid.old -text "Old version" \
-command changediffdisp -variable diffelide -value {0 1}
radiobutton .bleft.mid.new -text "New version" \
-command changediffdisp -variable diffelide -value {1 0}
pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left
set ctext .bleft.ctext
text $ctext -background $bgcolor -foreground $fgcolor \
-state disabled -font $textfont \
-yscrollcommand scrolltext -wrap none
scrollbar .bleft.sb -command "$ctext yview"
pack .bleft.top -side top -fill x
pack .bleft.mid -side top -fill x
pack .bleft.sb -side right -fill y
pack $ctext -side left -fill both -expand 1
lappend bglist $ctext
@@ -4487,6 +4496,13 @@ proc getblobdiffline {bdf ids} {
}
}
proc changediffdisp {} {
global ctext diffelide
$ctext tag conf d0 -elide [lindex $diffelide 0]
$ctext tag conf d1 -elide [lindex $diffelide 1]
}
proc prevfile {} {
global difffilestart ctext
set prev [lindex $difffilestart 0]
@@ -6331,6 +6347,7 @@ set highlight_paths {}
set searchdirn -forwards
set boldrows {}
set boldnamerows {}
set diffelide {0 0}
set optim_delay 16

View File

@@ -1,6 +1,5 @@
body {
font-family: sans-serif;
font-size: 12px;
border: solid #d9d8d1;
border-width: 1px;
margin: 10px;
@@ -31,7 +30,7 @@ img.logo {
div.page_header {
height: 25px;
padding: 8px;
font-size: 18px;
font-size: 150%;
font-weight: bold;
background-color: #d9d8d1;
}
@@ -113,7 +112,7 @@ span.signoff {
div.log_link {
padding: 0px 8px;
font-size: 10px;
font-size: 70%;
font-family: sans-serif;
font-style: normal;
position: relative;
@@ -181,19 +180,36 @@ table.diff_tree {
font-family: monospace;
}
table.combined.diff_tree td {
padding-right: 24px;
}
table.combined.diff_tree td.link {
padding: 0px 2px;
}
table.combined.diff_tree td.nochange a {
color: #6666ff;
}
table.combined.diff_tree td.nochange a:hover,
table.combined.diff_tree td.nochange a:visited {
color: #d06666;
}
table.blame {
border-collapse: collapse;
}
table.blame td {
padding: 0px 5px;
font-size: 12px;
font-size: 100%;
vertical-align: top;
}
th {
padding: 2px 5px;
font-size: 12px;
font-size: 100%;
text-align: left;
}
@@ -215,14 +231,14 @@ tr.dark:hover {
td {
padding: 2px 5px;
font-size: 12px;
font-size: 100%;
vertical-align: top;
}
td.link, td.selflink {
padding: 2px 5px;
font-family: sans-serif;
font-size: 10px;
font-size: 70%;
}
td.selflink {
@@ -399,7 +415,7 @@ div.index_include {
}
div.search {
font-size: 12px;
font-size: 100%;
font-weight: normal;
margin: 4px 8px;
position: absolute;
@@ -427,7 +443,7 @@ a.rss_logo {
background-color: #ff6600;
font-weight: bold;
font-family: sans-serif;
font-size: 10px;
font-size: 70%;
text-align: center;
text-decoration: none;
}
@@ -438,7 +454,7 @@ a.rss_logo:hover {
span.refs span {
padding: 0px 4px;
font-size: 10px;
font-size: 70%;
font-weight: normal;
border: 1px solid;
background-color: #ffaaff;

View File

@@ -458,10 +458,16 @@ my %actions = (
"project_index" => \&git_project_index,
);
if (defined $project) {
$action ||= 'summary';
} else {
$action ||= 'project_list';
if (!defined $action) {
if (defined $hash) {
$action = git_get_type($hash);
} elsif (defined $hash_base && defined $file_name) {
$action = git_get_type("$hash_base:$file_name");
} elsif (defined $project) {
$action = 'summary';
} else {
$action = 'project_list';
}
}
if (!defined($actions{$action})) {
die_error(undef, "Unknown action");
@@ -897,19 +903,34 @@ sub format_subject_html {
sub format_diff_line {
my $line = shift;
my ($from, $to) = @_;
my $char = substr($line, 0, 1);
my $diff_class = "";
chomp $line;
if ($char eq '+') {
$diff_class = " add";
} elsif ($char eq "-") {
$diff_class = " rem";
} elsif ($char eq "@") {
$diff_class = " chunk_header";
} elsif ($char eq "\\") {
$diff_class = " incomplete";
if ($from && $to && ref($from->{'href'}) eq "ARRAY") {
# combined diff
my $prefix = substr($line, 0, scalar @{$from->{'href'}});
if ($line =~ m/^\@{3}/) {
$diff_class = " chunk_header";
} elsif ($line =~ m/^\\/) {
$diff_class = " incomplete";
} elsif ($prefix =~ tr/+/+/) {
$diff_class = " add";
} elsif ($prefix =~ tr/-/-/) {
$diff_class = " rem";
}
} else {
# assume ordinary diff
my $char = substr($line, 0, 1);
if ($char eq '+') {
$diff_class = " add";
} elsif ($char eq '-') {
$diff_class = " rem";
} elsif ($char eq '@') {
$diff_class = " chunk_header";
} elsif ($char eq "\\") {
$diff_class = " incomplete";
}
}
$line = untabify($line);
if ($from && $to && $line =~ m/^\@{2} /) {
@@ -930,6 +951,39 @@ sub format_diff_line {
$line = "<span class=\"chunk_info\">@@ $from_text $to_text @@</span>" .
"<span class=\"section\">" . esc_html($section, -nbsp=>1) . "</span>";
return "<div class=\"diff$diff_class\">$line</div>\n";
} elsif ($from && $to && $line =~ m/^\@{3}/) {
my ($prefix, $ranges, $section) = $line =~ m/^(\@+) (.*?) \@+(.*)$/;
my (@from_text, @from_start, @from_nlines, $to_text, $to_start, $to_nlines);
@from_text = split(' ', $ranges);
for (my $i = 0; $i < @from_text; ++$i) {
($from_start[$i], $from_nlines[$i]) =
(split(',', substr($from_text[$i], 1)), 0);
}
$to_text = pop @from_text;
$to_start = pop @from_start;
$to_nlines = pop @from_nlines;
$line = "<span class=\"chunk_info\">$prefix ";
for (my $i = 0; $i < @from_text; ++$i) {
if ($from->{'href'}[$i]) {
$line .= $cgi->a({-href=>"$from->{'href'}[$i]#l$from_start[$i]",
-class=>"list"}, $from_text[$i]);
} else {
$line .= $from_text[$i];
}
$line .= " ";
}
if ($to->{'href'}) {
$line .= $cgi->a({-href=>"$to->{'href'}#l$to_start",
-class=>"list"}, $to_text);
} else {
$line .= $to_text;
}
$line .= " $prefix</span>" .
"<span class=\"section\">" . esc_html($section, -nbsp=>1) . "</span>";
return "<div class=\"diff$diff_class\">$line</div>\n";
}
return "<div class=\"diff$diff_class\">" . esc_html($line, -nbsp=>1) . "</div>\n";
}
@@ -1015,6 +1069,30 @@ sub git_get_hash_by_path {
return $3;
}
# get path of entry with given hash at given tree-ish (ref)
# used to get 'from' filename for combined diff (merge commit) for renames
sub git_get_path_by_hash {
my $base = shift || return;
my $hash = shift || return;
local $/ = "\0";
open my $fd, "-|", git_cmd(), "ls-tree", '-r', '-t', '-z', $base
or return undef;
while (my $line = <$fd>) {
chomp $line;
#'040000 tree 595596a6a9117ddba9fe379b6b012b558bac8423 gitweb'
#'100644 blob e02e90f0429be0d2a69b76571101f20b8f75530f gitweb/README'
if ($line =~ m/(?:[0-9]+) (?:.+) $hash\t(.+)$/) {
close $fd;
return $1;
}
}
close $fd;
return undef;
}
## ......................................................................
## git utility functions, directly accessing git repository
@@ -1495,6 +1573,17 @@ sub parse_difftree_raw_line {
$res{'file'} = unquote($7);
}
}
# '::100755 100755 100755 60e79ca1b01bc8b057abe17ddab484699a7f5fdb 94067cc5f73388f33722d52ae02f44692bc07490 94067cc5f73388f33722d52ae02f44692bc07490 MR git-gui/git-gui.sh'
# combined diff (for merge commit)
elsif ($line =~ s/^(::+)((?:[0-7]{6} )+)((?:[0-9a-fA-F]{40} )+)([a-zA-Z]+)\t(.*)$//) {
$res{'nparents'} = length($1);
$res{'from_mode'} = [ split(' ', $2) ];
$res{'to_mode'} = pop @{$res{'from_mode'}};
$res{'from_id'} = [ split(' ', $3) ];
$res{'to_id'} = pop @{$res{'from_id'}};
$res{'status'} = [ split('', $4) ];
$res{'to_file'} = unquote($5);
}
# 'c512b523472485aef4fff9e57b229d9d243c967f'
elsif ($line =~ m/^([0-9a-fA-F]{40})$/) {
$res{'commit'} = $1;
@@ -2198,8 +2287,42 @@ sub git_print_tree_entry {
## ......................................................................
## functions printing large fragments of HTML
sub fill_from_file_info {
my ($diff, @parents) = @_;
$diff->{'from_file'} = [ ];
$diff->{'from_file'}[$diff->{'nparents'} - 1] = undef;
for (my $i = 0; $i < $diff->{'nparents'}; $i++) {
if ($diff->{'status'}[$i] eq 'R' ||
$diff->{'status'}[$i] eq 'C') {
$diff->{'from_file'}[$i] =
git_get_path_by_hash($parents[$i], $diff->{'from_id'}[$i]);
}
}
return $diff;
}
# parameters can be strings, or references to arrays of strings
sub from_ids_eq {
my ($a, $b) = @_;
if (ref($a) eq "ARRAY" && ref($b) eq "ARRAY" && @$a == @$b) {
for (my $i = 0; $i < @$a; ++$i) {
return 0 unless ($a->[$i] eq $b->[$i]);
}
return 1;
} elsif (!ref($a) && !ref($b)) {
return $a eq $b;
} else {
return 0;
}
}
sub git_difftree_body {
my ($difftree, $hash, $parent) = @_;
my ($difftree, $hash, @parents) = @_;
my ($parent) = $parents[0];
my ($have_blame) = gitweb_check_feature('blame');
print "<div class=\"list_head\">\n";
if ($#{$difftree} > 10) {
@@ -2207,11 +2330,19 @@ sub git_difftree_body {
}
print "</div>\n";
print "<table class=\"diff_tree\">\n";
print "<table class=\"" .
(@parents > 1 ? "combined " : "") .
"diff_tree\">\n";
my $alternate = 1;
my $patchno = 0;
foreach my $line (@{$difftree}) {
my %diff = parse_difftree_raw_line($line);
my $diff;
if (ref($line) eq "HASH") {
# pre-parsed (or generated by hand)
$diff = $line;
} else {
$diff = parse_difftree_raw_line($line);
}
if ($alternate) {
print "<tr class=\"dark\">\n";
@@ -2220,31 +2351,120 @@ sub git_difftree_body {
}
$alternate ^= 1;
if (exists $diff->{'nparents'}) { # combined diff
fill_from_file_info($diff, @parents)
unless exists $diff->{'from_file'};
if ($diff->{'to_id'} ne ('0' x 40)) {
# file exists in the result (child) commit
print "<td>" .
$cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
file_name=>$diff->{'to_file'},
hash_base=>$hash),
-class => "list"}, esc_path($diff->{'to_file'})) .
"</td>\n";
} else {
print "<td>" .
esc_path($diff->{'to_file'}) .
"</td>\n";
}
if ($action eq 'commitdiff') {
# link to patch
$patchno++;
print "<td class=\"link\">" .
$cgi->a({-href => "#patch$patchno"}, "patch") .
" | " .
"</td>\n";
}
my $has_history = 0;
my $not_deleted = 0;
for (my $i = 0; $i < $diff->{'nparents'}; $i++) {
my $hash_parent = $parents[$i];
my $from_hash = $diff->{'from_id'}[$i];
my $from_path = $diff->{'from_file'}[$i];
my $status = $diff->{'status'}[$i];
$has_history ||= ($status ne 'A');
$not_deleted ||= ($status ne 'D');
if ($status eq 'A') {
print "<td class=\"link\" align=\"right\"> | </td>\n";
} elsif ($status eq 'D') {
print "<td class=\"link\">" .
$cgi->a({-href => href(action=>"blob",
hash_base=>$hash,
hash=>$from_hash,
file_name=>$from_path)},
"blob" . ($i+1)) .
" | </td>\n";
} else {
if ($diff->{'to_id'} eq $from_hash) {
print "<td class=\"link nochange\">";
} else {
print "<td class=\"link\">";
}
print $cgi->a({-href => href(action=>"blobdiff",
hash=>$diff->{'to_id'},
hash_parent=>$from_hash,
hash_base=>$hash,
hash_parent_base=>$hash_parent,
file_name=>$diff->{'to_file'},
file_parent=>$from_path)},
"diff" . ($i+1)) .
" | </td>\n";
}
}
print "<td class=\"link\">";
if ($not_deleted) {
print $cgi->a({-href => href(action=>"blob",
hash=>$diff->{'to_id'},
file_name=>$diff->{'to_file'},
hash_base=>$hash)},
"blob");
print " | " if ($has_history);
}
if ($has_history) {
print $cgi->a({-href => href(action=>"history",
file_name=>$diff->{'to_file'},
hash_base=>$hash)},
"history");
}
print "</td>\n";
print "</tr>\n";
next; # instead of 'else' clause, to avoid extra indent
}
# else ordinary diff
my ($to_mode_oct, $to_mode_str, $to_file_type);
my ($from_mode_oct, $from_mode_str, $from_file_type);
if ($diff{'to_mode'} ne ('0' x 6)) {
$to_mode_oct = oct $diff{'to_mode'};
if ($diff->{'to_mode'} ne ('0' x 6)) {
$to_mode_oct = oct $diff->{'to_mode'};
if (S_ISREG($to_mode_oct)) { # only for regular file
$to_mode_str = sprintf("%04o", $to_mode_oct & 0777); # permission bits
}
$to_file_type = file_type($diff{'to_mode'});
$to_file_type = file_type($diff->{'to_mode'});
}
if ($diff{'from_mode'} ne ('0' x 6)) {
$from_mode_oct = oct $diff{'from_mode'};
if ($diff->{'from_mode'} ne ('0' x 6)) {
$from_mode_oct = oct $diff->{'from_mode'};
if (S_ISREG($to_mode_oct)) { # only for regular file
$from_mode_str = sprintf("%04o", $from_mode_oct & 0777); # permission bits
}
$from_file_type = file_type($diff{'from_mode'});
$from_file_type = file_type($diff->{'from_mode'});
}
if ($diff{'status'} eq "A") { # created
if ($diff->{'status'} eq "A") { # created
my $mode_chng = "<span class=\"file_status new\">[new $to_file_type";
$mode_chng .= " with mode: $to_mode_str" if $to_mode_str;
$mode_chng .= "]</span>";
print "<td>";
print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
hash_base=>$hash, file_name=>$diff{'file'}),
-class => "list"}, esc_path($diff{'file'}));
print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
hash_base=>$hash, file_name=>$diff->{'file'}),
-class => "list"}, esc_path($diff->{'file'}));
print "</td>\n";
print "<td>$mode_chng</td>\n";
print "<td class=\"link\">";
@@ -2254,17 +2474,17 @@ sub git_difftree_body {
print $cgi->a({-href => "#patch$patchno"}, "patch");
print " | ";
}
print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
hash_base=>$hash, file_name=>$diff{'file'})},
print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
hash_base=>$hash, file_name=>$diff->{'file'})},
"blob");
print "</td>\n";
} elsif ($diff{'status'} eq "D") { # deleted
} elsif ($diff->{'status'} eq "D") { # deleted
my $mode_chng = "<span class=\"file_status deleted\">[deleted $from_file_type]</span>";
print "<td>";
print $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'},
hash_base=>$parent, file_name=>$diff{'file'}),
-class => "list"}, esc_path($diff{'file'}));
print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'from_id'},
hash_base=>$parent, file_name=>$diff->{'file'}),
-class => "list"}, esc_path($diff->{'file'}));
print "</td>\n";
print "<td>$mode_chng</td>\n";
print "<td class=\"link\">";
@@ -2274,22 +2494,22 @@ sub git_difftree_body {
print $cgi->a({-href => "#patch$patchno"}, "patch");
print " | ";
}
print $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'},
hash_base=>$parent, file_name=>$diff{'file'})},
print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'from_id'},
hash_base=>$parent, file_name=>$diff->{'file'})},
"blob") . " | ";
if ($have_blame) {
print $cgi->a({-href => href(action=>"blame", hash_base=>$parent,
file_name=>$diff{'file'})},
file_name=>$diff->{'file'})},
"blame") . " | ";
}
print $cgi->a({-href => href(action=>"history", hash_base=>$parent,
file_name=>$diff{'file'})},
file_name=>$diff->{'file'})},
"history");
print "</td>\n";
} elsif ($diff{'status'} eq "M" || $diff{'status'} eq "T") { # modified, or type changed
} elsif ($diff->{'status'} eq "M" || $diff->{'status'} eq "T") { # modified, or type changed
my $mode_chnge = "";
if ($diff{'from_mode'} != $diff{'to_mode'}) {
if ($diff->{'from_mode'} != $diff->{'to_mode'}) {
$mode_chnge = "<span class=\"file_status mode_chnge\">[changed";
if ($from_file_type ne $to_file_type) {
$mode_chnge .= " from $from_file_type to $to_file_type";
@@ -2304,9 +2524,9 @@ sub git_difftree_body {
$mode_chnge .= "]</span>\n";
}
print "<td>";
print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
hash_base=>$hash, file_name=>$diff{'file'}),
-class => "list"}, esc_path($diff{'file'}));
print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
hash_base=>$hash, file_name=>$diff->{'file'}),
-class => "list"}, esc_path($diff->{'file'}));
print "</td>\n";
print "<td>$mode_chnge</td>\n";
print "<td class=\"link\">";
@@ -2315,70 +2535,70 @@ sub git_difftree_body {
$patchno++;
print $cgi->a({-href => "#patch$patchno"}, "patch") .
" | ";
} elsif ($diff{'to_id'} ne $diff{'from_id'}) {
} elsif ($diff->{'to_id'} ne $diff->{'from_id'}) {
# "commit" view and modified file (not onlu mode changed)
print $cgi->a({-href => href(action=>"blobdiff",
hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'},
hash=>$diff->{'to_id'}, hash_parent=>$diff->{'from_id'},
hash_base=>$hash, hash_parent_base=>$parent,
file_name=>$diff{'file'})},
file_name=>$diff->{'file'})},
"diff") .
" | ";
}
print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
hash_base=>$hash, file_name=>$diff{'file'})},
print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
hash_base=>$hash, file_name=>$diff->{'file'})},
"blob") . " | ";
if ($have_blame) {
print $cgi->a({-href => href(action=>"blame", hash_base=>$hash,
file_name=>$diff{'file'})},
file_name=>$diff->{'file'})},
"blame") . " | ";
}
print $cgi->a({-href => href(action=>"history", hash_base=>$hash,
file_name=>$diff{'file'})},
file_name=>$diff->{'file'})},
"history");
print "</td>\n";
} elsif ($diff{'status'} eq "R" || $diff{'status'} eq "C") { # renamed or copied
} elsif ($diff->{'status'} eq "R" || $diff->{'status'} eq "C") { # renamed or copied
my %status_name = ('R' => 'moved', 'C' => 'copied');
my $nstatus = $status_name{$diff{'status'}};
my $nstatus = $status_name{$diff->{'status'}};
my $mode_chng = "";
if ($diff{'from_mode'} != $diff{'to_mode'}) {
if ($diff->{'from_mode'} != $diff->{'to_mode'}) {
# mode also for directories, so we cannot use $to_mode_str
$mode_chng = sprintf(", mode: %04o", $to_mode_oct & 0777);
}
print "<td>" .
$cgi->a({-href => href(action=>"blob", hash_base=>$hash,
hash=>$diff{'to_id'}, file_name=>$diff{'to_file'}),
-class => "list"}, esc_path($diff{'to_file'})) . "</td>\n" .
hash=>$diff->{'to_id'}, file_name=>$diff->{'to_file'}),
-class => "list"}, esc_path($diff->{'to_file'})) . "</td>\n" .
"<td><span class=\"file_status $nstatus\">[$nstatus from " .
$cgi->a({-href => href(action=>"blob", hash_base=>$parent,
hash=>$diff{'from_id'}, file_name=>$diff{'from_file'}),
-class => "list"}, esc_path($diff{'from_file'})) .
" with " . (int $diff{'similarity'}) . "% similarity$mode_chng]</span></td>\n" .
hash=>$diff->{'from_id'}, file_name=>$diff->{'from_file'}),
-class => "list"}, esc_path($diff->{'from_file'})) .
" with " . (int $diff->{'similarity'}) . "% similarity$mode_chng]</span></td>\n" .
"<td class=\"link\">";
if ($action eq 'commitdiff') {
# link to patch
$patchno++;
print $cgi->a({-href => "#patch$patchno"}, "patch") .
" | ";
} elsif ($diff{'to_id'} ne $diff{'from_id'}) {
} elsif ($diff->{'to_id'} ne $diff->{'from_id'}) {
# "commit" view and modified file (not only pure rename or copy)
print $cgi->a({-href => href(action=>"blobdiff",
hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'},
hash=>$diff->{'to_id'}, hash_parent=>$diff->{'from_id'},
hash_base=>$hash, hash_parent_base=>$parent,
file_name=>$diff{'to_file'}, file_parent=>$diff{'from_file'})},
file_name=>$diff->{'to_file'}, file_parent=>$diff->{'from_file'})},
"diff") .
" | ";
}
print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
hash_base=>$parent, file_name=>$diff{'to_file'})},
print $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
hash_base=>$parent, file_name=>$diff->{'to_file'})},
"blob") . " | ";
if ($have_blame) {
print $cgi->a({-href => href(action=>"blame", hash_base=>$hash,
file_name=>$diff{'to_file'})},
file_name=>$diff->{'to_file'})},
"blame") . " | ";
}
print $cgi->a({-href => href(action=>"history", hash_base=>$hash,
file_name=>$diff{'to_file'})},
file_name=>$diff->{'to_file'})},
"history");
print "</td>\n";
@@ -2389,7 +2609,8 @@ sub git_difftree_body {
}
sub git_patchset_body {
my ($fd, $difftree, $hash, $hash_parent) = @_;
my ($fd, $difftree, $hash, @hash_parents) = @_;
my ($hash_parent) = $hash_parents[0];
my $patch_idx = 0;
my $patch_number = 0;
@@ -2427,6 +2648,9 @@ sub git_patchset_body {
if ($patch_line =~ m/^index ([0-9a-fA-F]{40})..([0-9a-fA-F]{40})/) {
$from_id = $1;
$to_id = $2;
} elsif ($patch_line =~ m/^index ((?:[0-9a-fA-F]{40},)+[0-9a-fA-F]{40})..([0-9a-fA-F]{40})/) {
$from_id = [ split(',', $1) ];
$to_id = $2;
}
push @diff_header, $patch_line;
@@ -2436,8 +2660,8 @@ sub git_patchset_body {
# check if current patch belong to current raw line
# and parse raw git-diff line if needed
if (defined $diffinfo &&
$diffinfo->{'from_id'} eq $from_id &&
$diffinfo->{'to_id'} eq $to_id) {
from_ids_eq($diffinfo->{'from_id'}, $from_id) &&
$diffinfo->{'to_id'} eq $to_id) {
# this is split patch
print "<div class=\"patch cont\">\n";
} else {
@@ -2451,15 +2675,34 @@ sub git_patchset_body {
} else {
$diffinfo = parse_difftree_raw_line($difftree->[$patch_idx]);
}
$from{'file'} = $diffinfo->{'from_file'} || $diffinfo->{'file'};
$to{'file'} = $diffinfo->{'to_file'} || $diffinfo->{'file'};
if ($diffinfo->{'status'} ne "A") { # not new (added) file
$from{'href'} = href(action=>"blob", hash_base=>$hash_parent,
hash=>$diffinfo->{'from_id'},
file_name=>$from{'file'});
if ($diffinfo->{'nparents'}) {
# combined diff
$from{'file'} = [];
$from{'href'} = [];
fill_from_file_info($diffinfo, @hash_parents)
unless exists $diffinfo->{'from_file'};
for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
$from{'file'}[$i] = $diffinfo->{'from_file'}[$i] || $diffinfo->{'to_file'};
if ($diffinfo->{'status'}[$i] ne "A") { # not new (added) file
$from{'href'}[$i] = href(action=>"blob",
hash_base=>$hash_parents[$i],
hash=>$diffinfo->{'from_id'}[$i],
file_name=>$from{'file'}[$i]);
} else {
$from{'href'}[$i] = undef;
}
}
} else {
delete $from{'href'};
$from{'file'} = $diffinfo->{'from_file'} || $diffinfo->{'file'};
if ($diffinfo->{'status'} ne "A") { # not new (added) file
$from{'href'} = href(action=>"blob", hash_base=>$hash_parent,
hash=>$diffinfo->{'from_id'},
file_name=>$from{'file'});
} else {
delete $from{'href'};
}
}
$to{'file'} = $diffinfo->{'to_file'} || $diffinfo->{'file'};
if ($diffinfo->{'status'} ne "D") { # not deleted file
$to{'href'} = href(action=>"blob", hash_base=>$hash,
hash=>$diffinfo->{'to_id'},
@@ -2474,19 +2717,34 @@ sub git_patchset_body {
# print "git diff" header
$patch_line = shift @diff_header;
$patch_line =~ s!^(diff (.*?) )"?a/.*$!$1!;
if ($from{'href'}) {
$patch_line .= $cgi->a({-href => $from{'href'}, -class => "path"},
'a/' . esc_path($from{'file'}));
} else { # file was added
$patch_line .= 'a/' . esc_path($from{'file'});
}
$patch_line .= ' ';
if ($to{'href'}) {
$patch_line .= $cgi->a({-href => $to{'href'}, -class => "path"},
'b/' . esc_path($to{'file'}));
} else { # file was deleted
$patch_line .= 'b/' . esc_path($to{'file'});
if ($diffinfo->{'nparents'}) {
# combined diff
$patch_line =~ s!^(diff (.*?) )"?.*$!$1!;
if ($to{'href'}) {
$patch_line .= $cgi->a({-href => $to{'href'}, -class => "path"},
esc_path($to{'file'}));
} else { # file was deleted
$patch_line .= esc_path($to{'file'});
}
} else {
$patch_line =~ s!^(diff (.*?) )"?a/.*$!$1!;
if ($from{'href'}) {
$patch_line .= $cgi->a({-href => $from{'href'}, -class => "path"},
'a/' . esc_path($from{'file'}));
} else { # file was added
$patch_line .= 'a/' . esc_path($from{'file'});
}
$patch_line .= ' ';
if ($to{'href'}) {
$patch_line .= $cgi->a({-href => $to{'href'}, -class => "path"},
'b/' . esc_path($to{'file'}));
} else { # file was deleted
$patch_line .= 'b/' . esc_path($to{'file'});
}
}
print "<div class=\"diff header\">$patch_line</div>\n";
@@ -2503,14 +2761,37 @@ sub git_patchset_body {
$patch_line .= $cgi->a({-href=>$to{'href'}, -class=>"path"},
esc_path($to{'file'}));
}
# match <mode>
# match single <mode>
if ($patch_line =~ m/\s(\d{6})$/) {
$patch_line .= '<span class="info"> (' .
file_type_long($1) .
')</span>';
}
# match <hash>
if ($patch_line =~ m/^index/) {
if ($patch_line =~ m/^index [0-9a-fA-F]{40},[0-9a-fA-F]{40}/) {
# can match only for combined diff
$patch_line = 'index ';
for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
if ($from{'href'}[$i]) {
$patch_line .= $cgi->a({-href=>$from{'href'}[$i],
-class=>"hash"},
substr($diffinfo->{'from_id'}[$i],0,7));
} else {
$patch_line .= '0' x 7;
}
# separator
$patch_line .= ',' if ($i < $diffinfo->{'nparents'} - 1);
}
$patch_line .= '..';
if ($to{'href'}) {
$patch_line .= $cgi->a({-href=>$to{'href'}, -class=>"hash"},
substr($diffinfo->{'to_id'},0,7));
} else {
$patch_line .= '0' x 7;
}
} elsif ($patch_line =~ m/^index [0-9a-fA-F]{40}..[0-9a-fA-F]{40}/) {
# can match only for ordinary diff
my ($from_link, $to_link);
if ($from{'href'}) {
$from_link = $cgi->a({-href=>$from{'href'}, -class=>"hash"},
@@ -2546,7 +2827,8 @@ sub git_patchset_body {
}
next PATCH if ($patch_line =~ m/^diff /);
#assert($patch_line =~ m/^---/) if DEBUG;
if ($from{'href'} && $patch_line =~ m!^--- "?a/!) {
if (!$diffinfo->{'nparents'} && # not from-file line for combined diff
$from{'href'} && $patch_line =~ m!^--- "?a/!) {
$patch_line = '--- a/' .
$cgi->a({-href=>$from{'href'}, -class=>"path"},
esc_path($from{'file'}));
@@ -3750,14 +4032,13 @@ sub git_commit {
$parent = "--root";
}
my @difftree;
if (@$parents <= 1) {
# difftree output is not printed for merges
open my $fd, "-|", git_cmd(), "diff-tree", '-r', "--no-commit-id",
@diff_opts, $parent, $hash, "--"
or die_error(undef, "Open git-diff-tree failed");
@difftree = map { chomp; $_ } <$fd>;
close $fd or die_error(undef, "Reading git-diff-tree failed");
}
open my $fd, "-|", git_cmd(), "diff-tree", '-r', "--no-commit-id",
@diff_opts,
(@$parents <= 1 ? $parent : '-c'),
$hash, "--"
or die_error(undef, "Open git-diff-tree failed");
@difftree = map { chomp; $_ } <$fd>;
close $fd or die_error(undef, "Reading git-diff-tree failed");
# non-textual hash id's can be cached
my $expires;
@@ -3835,10 +4116,7 @@ sub git_commit {
git_print_log($co{'comment'});
print "</div>\n";
if (@$parents <= 1) {
# do not output difftree/whatchanged for merges
git_difftree_body(\@difftree, $hash, $parent);
}
git_difftree_body(\@difftree, $hash, @$parents);
git_footer_html();
}
@@ -4115,8 +4393,10 @@ sub git_commitdiff {
}
}
my $hash_parent_param = $hash_parent;
if (!defined $hash_parent) {
$hash_parent = $co{'parent'} || '--root';
$hash_parent_param =
@{$co{'parents'}} > 1 ? '-c' : $co{'parent'} || '--root';
}
# read commitdiff
@@ -4125,19 +4405,19 @@ sub git_commitdiff {
if ($format eq 'html') {
open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
"--no-commit-id", "--patch-with-raw", "--full-index",
$hash_parent, $hash, "--"
$hash_parent_param, $hash, "--"
or die_error(undef, "Open git-diff-tree failed");
while (my $line = <$fd>) {
chomp $line;
# empty line ends raw part of diff-tree output
last unless $line;
push @difftree, $line;
push @difftree, scalar parse_difftree_raw_line($line);
}
} elsif ($format eq 'plain') {
open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
'-p', $hash_parent, $hash, "--"
'-p', $hash_parent_param, $hash, "--"
or die_error(undef, "Open git-diff-tree failed");
} else {
@@ -4193,10 +4473,10 @@ TEXT
# write patch
if ($format eq 'html') {
git_difftree_body(\@difftree, $hash, $hash_parent);
git_difftree_body(\@difftree, $hash, $hash_parent || @{$co{'parents'}});
print "<br/>\n";
git_patchset_body($fd, \@difftree, $hash, $hash_parent);
git_patchset_body($fd, \@difftree, $hash, $hash_parent || @{$co{'parents'}});
close $fd;
print "</div>\n"; # class="page_body"
git_footer_html();

View File

@@ -516,9 +516,11 @@ sub config {
}
=item config_boolean ( VARIABLE )
=item config_bool ( VARIABLE )
Retrieve the boolean configuration C<VARIABLE>.
Retrieve the bool configuration C<VARIABLE>. The return value
is usable as a boolean in perl (and C<undef> if it's not defined,
of course).
Must be called on a repository instance.
@@ -526,14 +528,16 @@ This currently wraps command('config') so it is not so fast.
=cut
sub config_boolean {
sub config_bool {
my ($self, $var) = @_;
$self->repo_path()
or throw Error::Simple("not a repository");
try {
return $self->command_oneline('config', '--bool', '--get',
my $val = $self->command_oneline('config', '--bool', '--get',
$var);
return undef unless defined $val;
return $val eq 'true';
} catch Git::Error::Command with {
my $E = shift;
if ($E->value() == 1) {

180
t/t7300-clean.sh Executable file
View File

@@ -0,0 +1,180 @@
#!/bin/sh
#
# Copyright (c) 2007 Michael Spang
#
test_description='git-clean basic tests'
. ./test-lib.sh
test_expect_success 'setup' '
mkdir -p src &&
touch src/part1.c Makefile &&
echo build >.gitignore &&
echo \*.o >>.gitignore &&
git-add . &&
git-commit -m setup &&
touch src/part2.c README &&
git-add .
'
test_expect_success 'git-clean' '
mkdir -p build docs &&
touch a.out src/part3.c docs/manual.txt obj.o build/lib.so &&
git-clean &&
test -f Makefile &&
test -f README &&
test -f src/part1.c &&
test -f src/part2.c &&
test ! -f a.out &&
test ! -f src/part3.c &&
test -f docs/manual.txt &&
test -f obj.o &&
test -f build/lib.so
'
test_expect_success 'git-clean -n' '
mkdir -p build docs &&
touch a.out src/part3.c docs/manual.txt obj.o build/lib.so &&
git-clean -n &&
test -f Makefile &&
test -f README &&
test -f src/part1.c &&
test -f src/part2.c &&
test -f a.out &&
test -f src/part3.c &&
test -f docs/manual.txt &&
test -f obj.o &&
test -f build/lib.so
'
test_expect_success 'git-clean -d' '
mkdir -p build docs &&
touch a.out src/part3.c docs/manual.txt obj.o build/lib.so &&
git-clean -d &&
test -f Makefile &&
test -f README &&
test -f src/part1.c &&
test -f src/part2.c &&
test ! -f a.out &&
test ! -f src/part3.c &&
test ! -d docs &&
test -f obj.o &&
test -f build/lib.so
'
test_expect_success 'git-clean -x' '
mkdir -p build docs &&
touch a.out src/part3.c docs/manual.txt obj.o build/lib.so &&
git-clean -x &&
test -f Makefile &&
test -f README &&
test -f src/part1.c &&
test -f src/part2.c &&
test ! -f a.out &&
test ! -f src/part3.c &&
test -f docs/manual.txt &&
test ! -f obj.o &&
test -f build/lib.so
'
test_expect_success 'git-clean -d -x' '
mkdir -p build docs &&
touch a.out src/part3.c docs/manual.txt obj.o build/lib.so &&
git-clean -d -x &&
test -f Makefile &&
test -f README &&
test -f src/part1.c &&
test -f src/part2.c &&
test ! -f a.out &&
test ! -f src/part3.c &&
test ! -d docs &&
test ! -f obj.o &&
test ! -d build
'
test_expect_success 'git-clean -X' '
mkdir -p build docs &&
touch a.out src/part3.c docs/manual.txt obj.o build/lib.so &&
git-clean -X &&
test -f Makefile &&
test -f README &&
test -f src/part1.c &&
test -f src/part2.c &&
test -f a.out &&
test -f src/part3.c &&
test -f docs/manual.txt &&
test ! -f obj.o &&
test -f build/lib.so
'
test_expect_success 'git-clean -d -X' '
mkdir -p build docs &&
touch a.out src/part3.c docs/manual.txt obj.o build/lib.so &&
git-clean -d -X &&
test -f Makefile &&
test -f README &&
test -f src/part1.c &&
test -f src/part2.c &&
test -f a.out &&
test -f src/part3.c &&
test -f docs/manual.txt &&
test ! -f obj.o &&
test ! -d build
'
test_expect_success 'clean.requireForce' '
git-config clean.requireForce true &&
! git-clean
'
test_expect_success 'clean.requireForce and -n' '
mkdir -p build docs &&
touch a.out src/part3.c docs/manual.txt obj.o build/lib.so &&
git-clean -n &&
test -f Makefile &&
test -f README &&
test -f src/part1.c &&
test -f src/part2.c &&
test -f a.out &&
test -f src/part3.c &&
test -f docs/manual.txt &&
test -f obj.o &&
test -f build/lib.so
'
test_expect_success 'clean.requireForce and -f' '
git-clean -f &&
test -f README &&
test -f src/part1.c &&
test -f src/part2.c &&
test ! -f a.out &&
test ! -f src/part3.c &&
test -f docs/manual.txt &&
test -f obj.o &&
test -f build/lib.so
'
test_done

125
t/t9400-git-cvsserver-server.sh Executable file
View File

@@ -0,0 +1,125 @@
#!/bin/sh
#
# Copyright (c) 2007 Frank Lichtenheld
#
test_description='git-cvsserver access
tests read access to a git repository with the
cvs CLI client via git-cvsserver server'
. ./test-lib.sh
cvs >/dev/null 2>&1
if test $? -ne 1
then
test_expect_success 'skipping git-cvsserver tests, cvs not found' :
test_done
exit
fi
perl -e 'use DBI; use DBD::SQLite' >/dev/null 2>&1 || {
test_expect_success 'skipping git-cvsserver tests, Perl SQLite interface unavailable' :
test_done
exit
}
unset GIT_DIR GIT_CONFIG
WORKDIR=$(pwd)
SERVERDIR=$(pwd)/gitcvs.git
CVSROOT=":fork:$SERVERDIR"
CVSWORK=$(pwd)/cvswork
CVS_SERVER=git-cvsserver
export CVSROOT CVS_SERVER
rm -rf "$CVSWORK" "$SERVERDIR"
echo >empty &&
git add empty &&
git commit -q -m "First Commit" &&
git clone -q --local --bare "$WORKDIR/.git" "$SERVERDIR" >/dev/null 2>&1 &&
GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled true &&
GIT_DIR="$SERVERDIR" git config --bool gitcvs.logfile "$SERVERDIR/gitcvs.log" ||
exit 1
# note that cvs doesn't accept absolute pathnames
# as argument to co -d
test_expect_success 'basic checkout' \
'cvs -Q co -d cvswork master &&
test "$(echo $(grep -v ^D cvswork/CVS/Entries|cut -d/ -f2,3,5))" = "empty/1.1/"'
test_expect_success 'cvs update (create new file)' \
'echo testfile1 >testfile1 &&
git add testfile1 &&
git commit -q -m "Add testfile1" &&
git push gitcvs.git >/dev/null &&
cd cvswork &&
cvs -Q update &&
test "$(echo $(grep testfile1 CVS/Entries|cut -d/ -f2,3,5))" = "testfile1/1.1/" &&
diff -q testfile1 ../testfile1'
cd "$WORKDIR"
test_expect_success 'cvs update (update existing file)' \
'echo line 2 >>testfile1 &&
git add testfile1 &&
git commit -q -m "Append to testfile1" &&
git push gitcvs.git >/dev/null &&
cd cvswork &&
cvs -Q update &&
test "$(echo $(grep testfile1 CVS/Entries|cut -d/ -f2,3,5))" = "testfile1/1.2/" &&
diff -q testfile1 ../testfile1'
cd "$WORKDIR"
#TODO: cvsserver doesn't support update w/o -d
test_expect_failure "cvs update w/o -d doesn't create subdir (TODO)" \
'mkdir test &&
echo >test/empty &&
git add test &&
git commit -q -m "Single Subdirectory" &&
git push gitcvs.git >/dev/null &&
cd cvswork &&
cvs -Q update &&
test ! -d test'
cd "$WORKDIR"
test_expect_success 'cvs update (subdirectories)' \
'(for dir in A A/B A/B/C A/D E; do
mkdir $dir &&
echo "test file in $dir" >"$dir/file_in_$(echo $dir|sed -e "s#/# #g")" &&
git add $dir;
done) &&
git commit -q -m "deep sub directory structure" &&
git push gitcvs.git >/dev/null &&
cd cvswork &&
cvs -Q update -d &&
(for dir in A A/B A/B/C A/D E; do
filename="file_in_$(echo $dir|sed -e "s#/# #g")" &&
if test "$(echo $(grep -v ^D $dir/CVS/Entries|cut -d/ -f2,3,5))" = "$filename/1.1/" &&
diff -q "$dir/$filename" "../$dir/$filename"; then
:
else
echo >failure
fi
done) &&
test ! -f failure'
cd "$WORKDIR"
test_expect_success 'cvs update (delete file)' \
'git rm testfile1 &&
git commit -q -m "Remove testfile1" &&
git push gitcvs.git >/dev/null &&
cd cvswork &&
cvs -Q update &&
test -z "$(grep testfile1 CVS/Entries)" &&
test ! -f testfile1'
cd "$WORKDIR"
test_expect_success 'cvs update (re-add deleted file)' \
'echo readded testfile >testfile1 &&
git add testfile1 &&
git commit -q -m "Re-Add testfile1" &&
git push gitcvs.git >/dev/null &&
cd cvswork &&
cvs -Q update &&
test "$(echo $(grep testfile1 CVS/Entries|cut -d/ -f2,3,5))" = "testfile1/1.4/" &&
diff -q testfile1 ../testfile1'
test_done

4
utf8.c
View File

@@ -62,7 +62,7 @@ static int bisearch(ucs_char_t ucs, const struct interval *table, int max) {
* in ISO 10646.
*/
static int wcwidth(ucs_char_t ch)
static int git_wcwidth(ucs_char_t ch)
{
/*
* Sorted list of non-overlapping intervals of non-spacing characters,
@@ -207,7 +207,7 @@ invalid:
return 0;
}
return wcwidth(ch);
return git_wcwidth(ch);
}
int is_utf8(const char *text)