mirror of
https://github.com/git/git.git
synced 2026-03-13 10:23:30 +01:00
Merge GIT v1.5.2-rc3
This commit is contained in:
4
.mailmap
4
.mailmap
@@ -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>
|
||||
|
||||
30
Documentation/RelNotes-1.5.1.4.txt
Normal file
30
Documentation/RelNotes-1.5.1.4.txt
Normal 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.
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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::
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
|
||||
@@ -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
|
||||
===================================
|
||||
|
||||
|
||||
@@ -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='
|
||||
'
|
||||
|
||||
18
Makefile
18
Makefile
@@ -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)'
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
94
diff.c
@@ -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 */
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ static unsigned int contains(struct diff_filespec *one,
|
||||
}
|
||||
}
|
||||
}
|
||||
diff_free_filespec_data(one);
|
||||
return cnt;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
7
dir.c
@@ -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,
|
||||
|
||||
@@ -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
2
git-gui/.gitignore
vendored
@@ -1,3 +1,5 @@
|
||||
GIT-VERSION-FILE
|
||||
GIT-GUI-VARS
|
||||
git-citool
|
||||
git-gui
|
||||
lib/tclIndex
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
GVF=GIT-VERSION-FILE
|
||||
DEF_VER=0.6.GITGUI
|
||||
DEF_VER=0.7.GITGUI
|
||||
|
||||
LF='
|
||||
'
|
||||
|
||||
@@ -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
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
416
git-gui/lib/blame.tcl
Normal 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
572
git-gui/lib/branch.tcl
Normal 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
239
git-gui/lib/browser.tcl
Normal 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
154
git-gui/lib/class.tcl
Normal 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
393
git-gui/lib/commit.tcl
Normal 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
176
git-gui/lib/console.tcl
Normal 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
89
git-gui/lib/database.tcl
Normal 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
336
git-gui/lib/diff.tcl
Normal 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
101
git-gui/lib/error.tcl
Normal 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
409
git-gui/lib/index.tcl
Normal 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
312
git-gui/lib/merge.tcl
Normal 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
290
git-gui/lib/option.tcl
Normal 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
159
git-gui/lib/remote.tcl
Normal 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
141
git-gui/lib/shortcut.tcl
Normal 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
164
git-gui/lib/transport.tcl
Normal 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
|
||||
}
|
||||
@@ -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, $_;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
17
gitk
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
12
perl/Git.pm
12
perl/Git.pm
@@ -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
180
t/t7300-clean.sh
Executable 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
125
t/t9400-git-cvsserver-server.sh
Executable 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
4
utf8.c
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user