Compare commits

...
412 Commits
Author SHA1 Message Date
David Aguilar fde6ced1ed README: update the build instructions to mention GitPython
This removes the requirement notice on GitPython since it is
now built in to git-cola.  An acknowledgements was added instead.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-07-14 00:44:23 -07:00
David Aguilar 1f8777550a build: teach the build scripts about the bundled GitPython
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-07-14 00:41:06 -07:00
David Aguilar 4b8568dd82 tests: use the interal git module
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-07-14 00:40:31 -07:00
David Aguilar 4675fff67a cola: switch to using the internal git module
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-07-14 00:40:15 -07:00
David Aguilar 4f9c2d6174 cola: include GitPython to make installation simpler for users
We now ship a copy of GitPython so that users don't have to install
it themselves.  Another benefit is that we're shielded from API changes.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-07-14 00:39:31 -07:00
David Aguilar 3975b6e624 syntax: improve the whitespace checker
Previously, the syntax checker would only complain about the
final trailing whitespace character.  It now flags all of them.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-07-14 00:20:39 -07:00
David Aguilar 786132ca14 utils: rework fork()
We were getting some backtraces when launching apps in the
background with the subprocess-based fork() method.
This reworks the implementation to avoid subprocess.

The downside is that we now rely on shell_quote().
I'm probably doing something wrong, or there's some weirdness
introduced when running under PyQt.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-07-09 02:17:05 -07:00
David Aguilar 956e1bd6a6 models: fix (fetch|push|pull)_helper
I forgot to return status, which callers of fetch,
push and pull_helper expected to receive.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-07-09 01:51:23 -07:00
David Aguilar 4cd2e7fdc2 style: do the mainstream python thing and use spaces instead of tabs
No comment.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-07-06 14:51:56 -07:00
David Aguilar 815c654ddf syntax: use nicer colors for the diff display
This should really be a user preference...

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-07-03 01:46:32 -07:00
David Aguilar 97b1749b2b controllers: add support for using the diff editor to compare branches
This lets users launch a diff editor against files that are different
between their work tree and a specific branch.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-07-01 01:30:04 -07:00
David Aguilar df5dad891b ui: add a Branch->Launch Diff Editor Against... menu item
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-07-01 01:28:41 -07:00
David Aguilar d0b3e9eac3 models: properly detect copied files in parse_status()
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-07-01 01:28:12 -07:00
David Aguilar d1c2ae8b81 controllers: when restoring selection prefer unstaged files
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-07-01 01:27:54 -07:00
David Aguilar 071f77f8bf ui: add ItemsView and ComboView dialogs
These are simple dialogs for choosing items
from a list or a combo box.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-07-01 01:27:29 -07:00
David Aguilar f92236134a views: reworked item selection dialogs
There are now two simple dialogs for quickly
selecting items from a list.  One uses a qlistwidget
and the other uses a combobox.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-07-01 01:24:18 -07:00
David Aguilar 19761b2393 cola: centralized tmpfile management
git-cola now keeps temp files around until
the application is shut down.

This is more convenient since we no longer
have to worry about unlinking files.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-07-01 01:21:56 -07:00
David Aguilar cf3d0f3e7b models: update to the latest GitPython API (again)
GitPython removed get_git_dir() and get_work_tree().
git.Git() now requires that a path is passed to it as well.

All of cola's interaction with GitPython is now isolated
to the GitCola class.  This commit adds these useful
methods to the GitCola class.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-26 00:11:49 -07:00
David Aguilar bda109a267 scripts: update mktar-win32.sh to use a win32 subdirectory
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-24 02:24:34 -07:00
David Aguilar 8738342179 ui: enable the Branch -> Apply Diff From... menu action
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-24 02:21:59 -07:00
David Aguilar a29d12d0b2 controllers: generalized the diff gui for 'Apply Diff From Branch'
The diff gui can now apply diffs against arbitrary revisions.
This makes it possible to use git cola to do things like diff
branches, remotes, etc.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-23 23:46:06 -07:00
David Aguilar 45a91d1c48 stash: update to the latest GitPython API
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-23 23:45:05 -07:00
David Aguilar 56d70cd52d models: add an 'apply_diff_to_worktree' function
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-23 23:44:12 -07:00
David Aguilar b6fcad2b34 utils: generalize the diff parser
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-23 23:43:53 -07:00
David Aguilar 2ccb998dc5 ui: add an Apply Diffs From.. option to the branch menu
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-23 23:43:14 -07:00
David Aguilar b91534b983 models: update to the latest GitPython API
apollo13 changed GitPython so that with_exceptions=True
is the default.  Also, with_stderr no longer exists.
Instead, we can use with_extended_output.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-23 23:42:24 -07:00
David Aguilar fb519a7547 utils: add branch/rev support to the DiffParser class
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-21 03:13:22 -07:00
David Aguilar 2cba7e82d4 models: add branch/rev support to diff_helper
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-21 03:11:32 -07:00
David Aguilar e518655a78 GitPython: updated to use Govind's latest GitPython changes
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-21 00:46:15 -07:00
Barry Roberts 447d3d357e Add simplejson to install instructions 2008-06-17 22:54:07 -07:00
David Aguilar 38cff667e4 gui: avoid a win32 ui bug
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-13 03:35:05 -07:00
David Aguilar f6d08fdcea Added Barry Robert's Window install notes
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-13 03:32:44 -07:00
David Aguilar 2d8ba2cc95 git: use -v instead of --verbose to make older versions of git happy
git 1.5.3 doesn't understand "git add --verbose."

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-12 01:40:06 -07:00
David Aguilar 5cc1a8cefb README: URL updates
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-11 01:34:30 -07:00
David Aguilar 9ac3542781 README: Updated the win32 instructions
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-11 01:16:53 -07:00
David Aguilar 2803194de8 git-cola: a highly caffeinated git gui
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-10 03:05:36 -07:00
David Aguilar 24b259461d pull: make pull verbose
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-05 02:19:31 -07:00
David Aguilar 5b0991ef7f controllers: handle fetch/push/pull
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-05 02:09:54 -07:00
David Aguilar 1e0aa5688e ui: add fetch/push/pull buttons and a spacer
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-05 02:09:41 -07:00
David Aguilar 59e136dd62 views: add a RemoteView
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-05 02:06:10 -07:00
David Aguilar 279d081abc controllers: add a remote controller
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-05 02:05:38 -07:00
David Aguilar 08e4c40e5f ui: turn the push controller into a generic remote controller
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-05 02:05:10 -07:00
David Aguilar a4fd030521 qtutils: add raise_logger, optional args to show_output
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-05 02:04:14 -07:00
David Aguilar 165eece6da qobserver: add support for optional args in init()
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-05 02:03:52 -07:00
David Aguilar 111ceaae64 gitmodel: implement fetch/push/pull_helper()
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-05 01:58:49 -07:00
David Aguilar 8705543450 Oops, these should have been renamed too.
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-05 00:26:19 -07:00
David Aguilar 11a431f64f search: add export patches/cherry-pick functionality to search
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-03 03:25:49 -07:00
David Aguilar ccb8c0d805 models: add export_patchset helper
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-03 03:25:33 -07:00
David Aguilar 661ee8f5ce ui: finish the search gui, remove the merge button
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-03 03:24:56 -07:00
David Aguilar 66d8cff3c2 stash: don't close the gui on "Remove All"
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-03 03:24:00 -07:00
David Aguilar 9f7c343887 views: fix a bug in the view refactor
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-03 02:58:08 -07:00
David Aguilar ed1d53e230 stash: rearrange gui to save real estate
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-03 02:48:33 -07:00
David Aguilar 20317453d2 controllers: add a "launch editor" shortcut
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-03 02:35:45 -07:00
David Aguilar ee1adc1a30 controllers: add an "edit diff" launcher
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-03 02:35:31 -07:00
David Aguilar b7c16bfd25 options controller: update for diff/editor fields
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-03 02:31:35 -07:00
David Aguilar 73f3d7f470 ui: add diff/editor fields to the options gui
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-03 02:30:56 -07:00
David Aguilar 9a51e1e208 models: changed some config variable names, added editor/diffeditor
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-03 02:30:26 -07:00
David Aguilar 200ce7c759 README: updated to mention GitPython, LGPL
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-02 04:06:27 -07:00
David Aguilar 341775b106 models: fix a bug in format_patch_helper
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-02 03:02:13 -07:00
David Aguilar 199f0776f0 ui: remove some unused actions
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-02 02:54:05 -07:00
David Aguilar fe3a588bb8 ui: add the stash menu item
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-02 02:52:58 -07:00
David Aguilar dff52419e8 controllers: enable the stash gui
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-02 02:46:08 -07:00
David Aguilar 3ef83ae349 ui: add a Commit->Stash menu item
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-02 02:45:38 -07:00
David Aguilar 9fe33c71ca controllers: add a controller for the stash gui
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-02 02:44:20 -07:00
David Aguilar 8b5d640dfc utils: add a sanitize_input helper function
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-02 02:43:58 -07:00
David Aguilar 9f7a15eecc views: refactor the view classes
The view classes were all very similar, so a factory
method was created for generating view classes.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-02 02:43:27 -07:00
David Aguilar ea6b24d33e models: add a parse_stash_list helper function
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-02 02:38:52 -07:00
David Aguilar abff05d844 ui: add the stash dialog contributed by Barry Roberts
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-02 02:38:19 -07:00
David Aguilar f7007fdd46 models: fix a bug from the run_cmd/GitPython refactor
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-01 23:28:10 -07:00
David Aguilar 9e788bbbd9 ugit: remove python-git in favor of GitPython
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-01 23:15:57 -07:00
David Aguilar b3a030dcd1 tests: update git-ops test for GitPython
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-01 23:15:37 -07:00
David Aguilar 7199cf60bf utils: update to use GitPython
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-01 23:15:20 -07:00
David Aguilar 9044c8c267 models: update to use GitPython
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-01 23:15:10 -07:00
David Aguilar 6d36b6063b git-ugit: update to use GitPython
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-01 23:14:54 -07:00
David Aguilar 11097febd0 inotify: update to use GitPython
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-01 23:14:46 -07:00
David Aguilar 274b499c11 tests: update git-commands test to use GitPython
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-01 23:14:36 -07:00
David Aguilar d1bea4f005 build: remove python-git in favor of GitPython
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-01 23:14:15 -07:00
David Aguilar d276dc3b78 LICENSE: switch to LGPL3
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-01 23:13:57 -07:00
David Aguilar 1d13a5d8f7 debian: update debian version/changelog
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-01 23:13:49 -07:00
David Aguilar 2f69b507c7 controllers: fix hang on exit bug
ugit was previously hanging on exit.  This fixes it by
calling QMainWindow.close().

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-05-29 13:31:18 -07:00
David Aguilar 55c56aa7c6 export-patches: use the updated format_patch_helper
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-05-27 18:48:14 -07:00
David Aguilar a74427b99f python-git: make format_patch_helper better
format_patch_helper now creates patches that are suitable
for using with "git send-email patches/"

Commits that are selected in a range are exported as a
series of commits.  Single commits are exported individually.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-05-27 18:44:31 -07:00
David Aguilar dd677777e5 git.py: make "output" a keyword argument
This fixes issues when passing (output='foo', *args)

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-05-17 13:24:50 -07:00
David Aguilar 2d2d92cd83 controllers: fix format_patch_helper usage
The output field is now required by format_patch_helper.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-05-17 13:18:28 -07:00
David Aguilar 213ce45335 git.py: fix format_patch_helper
The output variable was being improperly reused due to a recent rename.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-05-17 13:06:26 -07:00
David Aguilar 72c90f3cca debian: update the changelog 2008-05-07 22:53:23 -07:00
David Aguilar a1a6150bea gitmodel: ensure we use Model.clone() instead of clone from git.py
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-05-07 22:40:51 -07:00
David Aguilar 57544ec5ed git.py: add the "rest" of the git commands
This adds the rest of the normal git commands so that git.<cmdname>
is available at import time for just about every git command available
from the cmdline.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-05-07 22:36:36 -07:00
David Aguilar 9eabca5907 push: rename model params to avoid conflicting with git.py
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-05-07 22:34:49 -07:00
David Aguilar b393c428cf git.py: add a docstring to the git_cmd closure
Even though the docstring is never seen, it still serves
as good documentation for the module's interface.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-05-07 03:21:59 -07:00
David Aguilar 616b49cd71 configure: allow $PYTHON to be overridden in the generated Makefile
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-05-07 03:03:50 -07:00
David Aguilar a157fb63ad README: update usage and mention the deb/rpms
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-05-07 02:58:54 -07:00
David Aguilar 5b5bcf8c51 wscript: only check env['PYTHON'] when python versions do not match
When running "configure" the waf script might be running under a different
version of python than the target for which python we're building.
We now detect "env PYTHON=/usr/bin/python2.4 waf configure" overrides.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-05-07 02:36:14 -07:00
David Aguilar e1bd33f5e5 git-ugit: setup sys.path for git.py
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-05-07 02:26:00 -07:00
David Aguilar 4694b35f89 python-git: fixed a bug in run_cmd
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-05-07 02:12:45 -07:00
David Aguilar 5963444b1b python-git: handle filenames with embedded quotes in their names
The git porcelain shell-wraps filenames with shell meta-characters.
We now detect this and use python's eval() to recover the
original filename.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-05-07 02:08:56 -07:00
David Aguilar 1efddbd4cb models: move the config functions closer to each other
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-05-07 02:05:48 -07:00
David Aguilar b99de6c069 models: add a comment, fix a typo
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-05-07 02:05:00 -07:00
David Aguilar f826db0420 configure: allow fine-grained control over python versions
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-05-07 01:44:50 -07:00
David Aguilar bd25e7101d configure: always regenerate the Makefile
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-05-07 01:44:23 -07:00
David Aguilar 32d4a33b6a git.py: update docstring for run_cmd
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-05-06 23:09:34 -07:00
David Aguilar f5485201fb debian: install git.py for both python2.4 and python2.5
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-05-06 23:09:15 -07:00
David Aguilar e30ccdc0b6 build: add a pycompile shell script
This is a simple script to compile python
files into .pyc/.pyo files.  It will be used
by the debian makefiles.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-05-06 23:07:50 -07:00
David Aguilar dfa3cbea14 debian: update changelog with proper signature
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-05-06 02:23:38 -07:00
David Aguilar 115f043eda debian: version 0.9.2
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-05-06 01:52:55 -07:00
David Aguilar fd1f23c7ac python: make git.py globally accessible
git.py is now installed in the standard python
site-packages directory.  This means that it is
possible to merely say:

	import git

to access the git.py module.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-05-06 01:48:55 -07:00
David Aguilar 51a7b47e60 models: set the global diff context during startup
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-05-06 00:57:54 -07:00
David Aguilar 46f2086b26 inotify: add a timeout to the inotify watcher
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-05-06 00:53:03 -07:00
David Aguilar 4d1780983b utils: remove QProcess usage
run_cmd and friends were moved to git.py.
Make utils redirect callers to git.py for
run_cmd and get_tmp_filename methods.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-05-06 00:52:23 -07:00
David Aguilar 8d893bb747 git.py: remove all external dependencies
The git.py module is now dependent on Python only.
Previously, it depended on the ugit.utils and PyQt4.QtCore
modules.  All process management is handled by subprocess.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-05-06 00:09:12 -07:00
David Aguilar 43b4794db7 tests: add a test summary to "make test"
"make test" now prints a summary at the end of a run.
"make test" exits on the first error it encounters.

To force running all tests, use:
	make RUN_ALL=1 test

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-05-01 02:39:06 -07:00
Kelvie Wong d484f1c012 Use the maxsplit argument to str.split when parsing config files.
This is so you can use the "=" symbol inside .gitconfig without raising a
ValueError.

Git seems to parse lines in gitconfig like:
	work-week = log --author=Kelvie --since=last.sunday
without any issue, so this seems appropriate.

Signed-off-by: Kelvie Wong <kelvie@ieee.org>
2008-04-30 02:04:57 -07:00
David Aguilar 20f00d3579 build: gracefully handle a missing python installation
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-28 23:10:40 -07:00
David Aguilar ac00e047a8 tests: add an import test, update the model api in model-save-restore
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-28 22:56:54 -07:00
David Aguilar 96e9d4e86e model: load() should import parameters in-place
This fixes an infinite recursion when loading the
SettingsModel() since that model's init() method
calls self.load(...)

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-28 22:54:54 -07:00
David Aguilar fbcf229f33 debian: have the mkdeb.sh script also generate rpms
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-26 07:06:38 -07:00
David Aguilar 3e0a1c933e debian: updated release to v0.9.1-x
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-26 07:03:19 -07:00
David Aguilar 867a8d929f updated version script
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-26 06:54:30 -07:00
David Aguilar 7f0d4d0597 tests: add a test-case for recursive structures
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-26 06:45:11 -07:00
David Aguilar 7c2e986ff3 model: improve the core model to allow recursive object structures
Previously, the logic for serializing/deserializing to/from dictionaries
did not properly deal with lists of hashes, hashes of lists, lists, etc.

The core model can now handle any arbitrarily complicated structure.
The core to_dict() routine recursively walks the object structure
without the need for external data for tracking parameter types.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-26 06:44:57 -07:00
David Aguilar a9f4f2828c tests: update tests for the new installation layout
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-26 06:35:57 -07:00
David Aguilar c55ca4dd8f inotify: simplify the code a bit
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-26 05:05:26 -07:00
David Aguilar 0e10109cee inotify: only watch directories
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-24 03:46:00 -07:00
David Aguilar 346c710374 use "git ugit" when spawning ugit sessions
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-24 03:26:26 -07:00
David Aguilar e7573f1459 bookmarks: fix menu action names
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-24 03:25:30 -07:00
David Aguilar 33b05527ef controllers: implement a bookmarks manager
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-22 05:22:25 -07:00
David Aguilar bee3e15896 views: add a bookmarks view
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-22 05:21:24 -07:00
David Aguilar 05d7239d7c qtutils: simplify qtutils.information()
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-22 05:21:02 -07:00
David Aguilar 90a5e49c0b model: rework model save/restore
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-22 05:19:52 -07:00
David Aguilar 94d3bc58e8 git-ugit: put ugit module path first in sys.path
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-22 05:18:58 -07:00
David Aguilar 4e4f523abb model: fail gracefully when simplejson is not installed
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-22 04:08:43 -07:00
David Aguilar f00bc82915 ugit: implement file->open repository
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-22 02:53:30 -07:00
David Aguilar 4440eeacee git-ugit: allow multiple repositories on the command line
The following form is now valid:
	git ugit /repo/path-1 /repo/path-2 ... /repo/path-N

A new ugit session is spawned for each repo.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-22 02:52:40 -07:00
David Aguilar bf9a846527 git-ugit: improve ugit's command line interface
The following form is now valid:
	git ugit /path/to/repo

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-22 02:23:27 -07:00
David Aguilar a12666ae42 git model: bail out immediately when we're not in a git repo
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-22 02:22:01 -07:00
David Aguilar 6317264a46 release: update version.sh to better handle tags
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-20 01:09:47 -07:00
David Aguilar 36739cf1aa UGIT 0.9
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-20 00:52:15 -07:00
David Aguilar 9d86a739ba controllers: do not require modified files when amending
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-20 00:52:05 -07:00
David Aguilar 39b72069d9 debian: properly cleanup ugit builds
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-20 00:46:15 -07:00
David Aguilar ac8236e426 debian: update build dependencies
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-20 00:30:44 -07:00
David Aguilar ef78d94225 release: use tag-based versioning
we now use a tag-based versioing scheme just like that of the
git project itself.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-20 00:24:00 -07:00
David Aguilar d0a839f3ce search: add a keybinding for the return key
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-19 23:34:29 -07:00
David Aguilar ba3716a8f9 search: don't automatically search when the user types
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-19 23:16:30 -07:00
David Aguilar cbad8ca2e0 .gitignore: add an entry for the /meta directory
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-19 19:00:45 -07:00
David Aguilar 8a199966d8 git-ugit: setup sys.path for the debianized module path
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-19 18:56:29 -07:00
David Aguilar 1c013213a9 controllers: raise the editor dock when an item iss staged
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-18 01:54:59 -07:00
David Aguilar 37c46e6b36 debian: comment the debian/rules
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-18 01:54:22 -07:00
David Aguilar a4fc242b83 fix refactoring bug in the search dialog 2008-04-15 03:14:44 -07:00
David Aguilar d16c8781e9 merge: add a commit checkbox to allow merging w/out commit
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-15 02:37:47 -07:00
David Aguilar 049bb53ab1 utils: fix fork() to properly pass argv[0]
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-15 02:37:03 -07:00
David Aguilar b366d30fe3 models: handle loading MERGE_MSG and SQUASH_MSG
ugit now detects both MERGE_MSG and SQUASH_MSG
and loads them into the commit message editor.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-15 02:36:40 -07:00
David Aguilar 9277ac0228 Updated debian changelog
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-14 04:43:21 -07:00
David Aguilar b651cfc9eb controllers: added a merge controller
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-14 04:38:21 -07:00
David Aguilar 04fd0fb96a controllers: implement abort merge
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-14 03:11:06 -07:00
David Aguilar 18e4af2e20 controllers: simplified qobserver startup
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-14 03:10:23 -07:00
David Aguilar 7801694f33 debian: ugit is now debianized
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-14 01:20:48 -07:00
David Aguilar c4332d4acb gui: added a merge menu
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-13 18:20:19 -07:00
David Aguilar 1ba197e3c2 build: simplify the win32 build script
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-13 18:14:43 -07:00
David Aguilar 7c760602ba build: use a common.sh file for common operations
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-13 18:02:58 -07:00
David Aguilar d982638c83 oops: fixed git reset usage
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-13 18:02:25 -07:00
David Aguilar fab3d86613 cmds: add rev_list to the git auto-cmd list
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-13 07:04:01 -07:00
David Aguilar d053611998 controllers: add a quick 'git grep' search menu helper
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-13 06:55:48 -07:00
David Aguilar 1e5f27ae44 win32: updated py2exe-setup.py for the git-ugit rename
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-13 06:52:35 -07:00
David Aguilar 72bfcc5217 commands: reworked to remove hardcoded method names from the model
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-13 06:52:16 -07:00
David Aguilar ca3dbb5a9b README: updated to mention "git ugit"
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-13 06:50:21 -07:00
David Aguilar 937779e74a qtutils: add a simple input dialog helper
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-13 06:49:35 -07:00
David Aguilar 9c8726e5d9 release: version up to 0.8.13
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-13 04:02:01 -07:00
David Aguilar 134cbcd90f controllers: implement search by committer
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-13 04:00:05 -07:00
David Aguilar cc2342eff4 controllers: implement search by author
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-13 03:58:20 -07:00
David Aguilar eddec93589 controllers: implement search by path
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-13 03:57:47 -07:00
David Aguilar e68feb7038 controllers: implement search by diff contents
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-13 03:56:50 -07:00
David Aguilar 0df6ec29c1 controllers: implement search by date range
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-13 03:55:33 -07:00
David Aguilar 74ce128384 controllers: implement search by commit message
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-13 03:54:04 -07:00
David Aguilar b626ccced9 controllers: implement search by revision range
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-13 03:53:04 -07:00
David Aguilar 3df0ba07f3 controllers: remove the old search-by-revision controller
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-13 03:52:14 -07:00
David Aguilar 1dc74c8576 controllers: implement search by revision using the uber-search dialog
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-13 03:51:21 -07:00
David Aguilar d1ce3d03e7 controllers: implement search by revision range
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-12 06:23:24 -07:00
David Aguilar 973f6d43df qobserver: add_actions syntax sugar
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-12 05:05:19 -07:00
David Aguilar 0cac581424 gui: renamed the gui modules for consistency
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-12 04:04:44 -07:00
David Aguilar ddf46de224 cleanup: remove some unused code
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-12 03:44:35 -07:00
David Aguilar a25deac906 layout: move views and controllers into a subdirectory
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-12 03:44:20 -07:00
David Aguilar c8f0b5b117 Add a merge button to the uber-search gui
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-12 02:20:06 -07:00
David Aguilar 39a0bdc3f2 ui: created a search dialog gui
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-08 00:34:04 -07:00
David Aguilar f0644cbeb2 controllers: rename "find" to "search" in prep for search menu work
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-07 02:44:23 -07:00
David Aguilar 6cc000c7cc controllers: improve the search by revision functionality
double-clicking in the revision list will now run
git format-patch on the selected revision.

some small quirks with the ui were also fixed.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-07 01:48:05 -07:00
David Aguilar e058abbd3e gui: add the proper menu action names for later use
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-07 01:46:12 -07:00
David Aguilar bc4fe64a4c s/log/log_helper/
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-06 06:38:49 -07:00
David Aguilar 61d224db37 cmds: dispatch ls_tree and s/parsed/parse/g
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-06 05:34:30 -07:00
David Aguilar 0f889991bd release: version up to 0.8.12
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-06 04:25:16 -07:00
David Aguilar b43aef3383 cmds: use the dispatcher with git push
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-06 04:09:58 -07:00
David Aguilar bb35347183 cmds: use the dispatcher with git log
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-06 04:07:49 -07:00
David Aguilar f6b3e091db cmds: simplify cherry-pick by using the dispatcher
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-06 03:57:58 -07:00
David Aguilar 7e1014b188 cmds: teach format-patch about the dispatcher
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-06 03:47:51 -07:00
David Aguilar bf230dffe7 cmds: teach checkout and remote about the dispatcher
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-06 03:40:36 -07:00
David Aguilar 1868f607a7 cmds: teach apply usage about the dispatcher
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-06 03:39:45 -07:00
David Aguilar 0d0ab3f7b7 cmds: teach diff usage about the dispatcher
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-06 03:39:05 -07:00
David Aguilar dd90636a57 cmds: teach cat-file usage about the dispatcher
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-06 03:35:54 -07:00
David Aguilar 549f512bfe cmds: teach ls_files usage about the dispatcher
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-06 03:34:26 -07:00
David Aguilar 7fa23fd86d cmds: teach add, reset, and rebase about the dispatcher
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-06 03:33:18 -07:00
David Aguilar 86392d8da5 cmds: teach config handling about the comman dispatcher
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-06 02:23:55 -07:00
David Aguilar 20d4fe0c90 tests: add git repo tests
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-05 19:02:02 -07:00
David Aguilar 50095592bb tests: add a "make test" target
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-05 19:01:31 -07:00
David Aguilar dafd93f819 tests: add a testutils module
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-05 19:01:15 -07:00
David Aguilar ad72543683 oops: fix rev-list by range
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-05 19:00:41 -07:00
David Aguilar 459ad5340d cmds: teach git.commit about the dispatcher
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-05 19:00:04 -07:00
David Aguilar 84bd89972d clean up the comments
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-05 16:56:40 -07:00
David Aguilar ba27341fac Make the status parser use the gitcmd dispatcher and rename it to parsed_status
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-05 16:56:06 -07:00
David Aguilar 64217dc172 git.py: create the command dispatcher earlier in the import phase
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-05 16:54:47 -07:00
David Aguilar b9b8d1b522 git.py: use a dispatcher to allow for more dynamism
The goal of these changes is to allow runtime creation
of arbitrary git commands.  e.g. git.branch() should
return the output of "git branch."  We do this by
using a module dispatcher/__getattr__ to intersept
attributes.  Eventually, the git.py module should
be very tiny as the hand-coded methods are
phased out.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-04 03:40:58 -07:00
David Aguilar a319f866c9 tests: add a run_tests convenience script
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-04 03:37:23 -07:00
David Aguilar 7be70df4db gui: finish the search by revision gui
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-02 03:14:55 -07:00
David Aguilar 88ffa84bce Vastly improve startup time by reducing notifications
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-04-02 02:45:54 -07:00
David Aguilar d0c295d1b5 utilcontroller: improve find by revision
Find by revision now returns multiple results and
allows the user to browse the found/matching commits.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-03-31 01:34:09 -07:00
David Aguilar 2da2f63fa6 gui: clicking on a file now raises the diff gui
ugit now raises the diff dockwindow when the view_diff()
action is triggered.  This works regardless of whether the diff display
is a standalone window or if it is in a hidden dock tab.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-03-31 00:52:18 -07:00
David Aguilar 931b711b24 ugit: add --version and --project options
ugit can be launched on projects in a different path
by pass --project /path/to/git/repo.

ugit --version now reports the current ugit version.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-03-31 00:48:43 -07:00
David Aguilar af39d7b2a8 release: update ugit version to 0.8.11
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-03-30 16:59:29 -07:00
David Aguilar edd9f09d33 ugit: remove the .py extension for git-ugit.py to make completion look nicer
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-03-30 16:50:01 -07:00
David Aguilar da992c5eac gui: remember the saveatexit setting on first use
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-03-30 16:28:17 -07:00
David Aguilar f6ac00fd23 gui: cleanup the interface by auto-setting the status view splitter orientation
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-03-30 15:33:35 -07:00
David Aguilar f865c21531 maint: move TODO into the maint branch
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-03-30 14:58:34 -07:00
David Aguilar 2d00b07a1d release: update ugit version to 0.8.10
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-03-26 04:38:23 -07:00
David Aguilar 1bdf4a8485 gui: improve the default layout
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-03-26 04:34:30 -07:00
David Aguilar 134d8fd62d build: properly detect dirty work tree in gitversion.sh
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-03-26 04:34:05 -07:00
David Aguilar 5e13fd7b11 build: add a git revision id to the ugit version number
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-03-26 03:05:44 -07:00
David Aguilar 1635615135 build: update build-win32.sh script for ugit rename
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-03-26 00:10:56 -07:00
David Aguilar 82fec5a1ab Renamed 'ugitlibs' to just plain 'ugit'
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-03-26 00:02:30 -07:00
David Aguilar 388824acc4 configure: match git's shell script style
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-03-25 21:56:09 -07:00
David Aguilar cbc48ae799 gui: improve layout options
The gui is now fully fluid.  Widgets can be placed anywhere and
torn off separately.  The "Horizontal Status" checkbox now controls
whether the status view is layed out horizontally or vertically.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-03-25 04:24:09 -07:00
David Aguilar 56d68e4ab7 gui: allow torn widgets to be on top
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-03-25 04:03:49 -07:00
David Aguilar 9ebcda2f63 Improve the config option handling
ugit now supports a flag to enable saving window settings:
ugit.savewindowsettings

whether to save these settings at exit is controlled by:
ugit.saveatexit

The git config interaction was also refactored so that the config
parsing is done entirely within the git module.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-03-25 03:57:19 -07:00
David Aguilar 33cbe8a644 gui: remove splitters and add a dockable layout
The main gui now has a fluid layout with widgets that can be
arranged and docked/undocked at runtime.  Support for saving
layouts will need to be reimplemented.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-03-24 03:37:06 -07:00
David Aguilar 8d9e90e5ac git: update 'git config' to use the refactored command line handler
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-03-24 03:02:41 -07:00
David Aguilar c0162c39f0 release: version up to 0.8.8
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-03-24 01:22:50 -07:00
David Aguilar 27b02d9038 model + controllers: fix bugs from run_cmd refactor
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-03-24 01:21:37 -07:00
David Aguilar f2a8d5b38d model+controllers: renamed changed to modified to match git terminology
model.get_modified() should make more sense than get_changed() since
modified is how git refers to these files in the documentation.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-03-24 00:25:32 -07:00
David Aguilar 285e830726 git: simplify command line interaction by refactoring run_cmd
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-03-24 00:17:17 -07:00
David Aguilar 4d68fd54ca controllers: fix reselect-on-rescan logic
The initial version of reselect-on-rescan was using the modified items
from the model instead of modified + untracked.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-03-24 00:16:09 -07:00
David Aguilar 64836d720d Teach the release scripts about ugitlibs/defaults.py
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-03-23 20:30:02 -07:00
David Aguilar 5061f1642a build: teach the system to use defaults.py for version info
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-03-23 20:16:32 -07:00
David Aguilar e00be9bc9f Updated README
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-03-23 14:15:37 -07:00
David Aguilar 75633bebde Increment ugit version to 0.8.7
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-03-23 05:48:14 -07:00
David Aguilar fcd011021d controllers: save and restore listview selection on rescan
We now restore the active selection when rescans occur.  Previously we would
lose the selection list and the scrollbar positioning.  All of these are now preserved.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-03-22 03:04:56 -07:00
David Aguilar 74a65e1247 controllers: trigger actions when listview item icons are clicked
Clicking a file's icon in the listview will now trigger the same action that
would happen if the row were double-clicked.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-03-22 03:03:34 -07:00
David Aguilar 00c49f2d33 views: set alternating row color in main list widgets
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-03-22 02:55:36 -07:00
David Aguilar e455dddd21 Increment ugit version to v0.8.6
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-02-25 01:30:07 -08:00
David Aguilar d98bf524ed Add a context menu to the unstaged item list
Right-clicking on the unstaged item list now displays two menu choices:
Stage Selected
Undo Local Changes

"Undo Local Changes" checks the file out from HEAD, thus dropping all
uncommitted changes.  The user is prompted before proceeding with
this action.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-02-25 01:28:29 -08:00
David Aguilar 0c42bd8dc5 Allow git.checkout to take accept arbitrary arguments
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-02-25 01:25:11 -08:00
David Aguilar 9a4d530126 Updated build scripts to use the version defined in the wscript
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-02-21 03:12:21 -08:00
David Aguilar c5edd7b961 Fix the non-ffwd push operation
This fixes a bug where a non-ffwd push cannot be performed when local_branch == remote_branch.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-02-20 23:44:18 -08:00
David Aguilar 4993d66235 Fix signoff message 2008-02-04 23:41:06 -08:00
David Aguilar 24e3823390 Use more robust regexes in the status parser
The 'git status' parser now uses regexes that are not dependent on
the variable whitespace present in git's output.

Signed-off by: David Aguilar <davvid@gmail.com>
2008-01-27 01:33:01 -08:00
David Aguilar 2e990b9938 Properly handle newly added files
This fixes the incorrect "new file: " regex.

Signed-off by: David Aguilar <davvid@gmail.com>
2008-01-27 00:57:20 -08:00
David Aguilar ad06fa4523 Added support for py2exe thanks to Sebastian Schuberth
ugit.py was adjusted to account for __file__ not being available when
using py2exe.  The py2exe setup scripts were provided by Sebastian for
inclusion with the win32 release.  scripts/build-win32.sh was adjusted
accordingly.

Signed-off by: David Aguilar <davvid@gmail.com>
2008-01-21 17:04:56 -08:00
David Aguilar c66b09440f Refactored the QObserver API
Subclasses are no longer required to call model_to_view.  If the convention of naming
view widgets the same as the model's parameters is followed, then a lazier
add_observables call can be used.  This is now used consistently throughout ugit.

Signed-off by: David Aguilar <davvid@gmail.com>
2008-01-20 17:42:54 -08:00
David Aguilar 2d033e1a3a Renamed controller.log_output(...) to controller.log(...)
Renaming this method makes the naming consistent between the
controller and qtutils module.

Signed-off by: David Aguilar <davvid@gmail.com>
2008-01-20 17:09:55 -08:00
David Aguilar a5c54b49ca Improved Windows support thanks to Sebastian Schuberth
script-ugit-cygwin.sh had a typo.  A better solution (that works on both cygwin and msys)
was proposed and added as ugit-win32.sh.  That script replaces both of the old ones.

Signed-off by: David Aguilar <davvid@gmail.com>
2008-01-20 14:01:50 -08:00
David Aguilar f7a752918d Increment ugit version and enable the default icon
ugit v0.8.2

Signed-off by: David Aguilar <davvid@gmail.com>
2008-01-15 05:02:35 -08:00
David Aguilar b219628708 Don't enable the diff gui when displaying the index
The interactive diff gui menu actions were still enabled when displaying a diffstat or
index.  The controller now tracks whether it's ok to enable patch selection,
and disables it for diffstats/index views.

Signed-off by: David Aguilar <davvid@gmail.com>
2008-01-15 05:02:00 -08:00
David Aguilar 4c541ad1d7 Add a "Show Index" menu action
This action displays the current state of the index in the diff window.
Basically, a syntax-highlighted view of git diff --cached --unified=<n> --stat

Signed-off by: David Aguilar <davvid@gmail.com>
2008-01-15 04:42:30 -08:00
David Aguilar 777588a38e Always close the logging window on exit
Previously the logging window had to be closed independently of the main dialog.
This fixes that.

Signed-off by: David Aguilar <davvid@gmail.com>
2008-01-15 04:37:09 -08:00
David Aguilar 1dd228f2f7 Add syntax highlighting for diffstats
This patch teaches the DiffSyntaxHighlighter to highlight git diffstats.
This was purely done by adding a new rule to the highlighter.
The GenericSyntaxHighlighter was also fixed to allow empty matches.
Some of the format variable names were changed to improve readability.

Signed-off by: David Aguilar <davvid@gmail.com>
2008-01-15 02:57:39 -08:00
David Aguilar 11a2afa6a1 Refactored QObserver class and removed lots of code
Any invocation of add_callbacks or model_to_view will now automatically handle
add_signals invocation for the widget of interest.  This removes almost every
explicit call to add_signals.  autoconnect() methods were added to the QObserver
to help facilitate this functionality.

Signed-off by: David Aguilar <davvid@gmail.com>
2008-01-15 01:45:23 -08:00
David Aguilar cb85b72bda Update the controllers to use the action dispatcher
Now that the action dispatcher properly behaves, update the controllers to use it
instead of using direct Qt signal/slot connections.

Signed-off by: David Aguilar <davvid@gmail.com>
2008-01-13 05:26:57 -08:00
David Aguilar ddd433a302 Properly undo changes when cancelling out of the options gui using the ESC key
Previously, hitting ESC to exit out of the options gui was not properly notifying
the main app about needing to update/revert the fonts back to their original values.
This fixes that problem and removes some code by specifying the connection between
the optionsgui.cancel_button signal and the optionsgui.reject slot in the ui file.
We then catch the reject signal in the controller and handle that.  The reason it
didn't work previously is that we were only handling the case were cancel was pressed
instead of explicitly handling the rejected() signal.

Signed-off by: David Aguilar <davvid@gmail.com>
2008-01-13 05:25:28 -08:00
David Aguilar f653ab09ed Use default font names that are recognized across more platforms
Using "helvetica" and "*mono" as the font names is better since
those are understood and translated into their native variants
on both Linux and OSX.

Signed-off by: David Aguilar <davvid@gmail.com>
2008-01-13 04:33:06 -08:00
David Aguilar 51291e33f4 Fixed the QObserver's action dispatcher
Previously, actions were only launched when there was a mapping between a model
parameter and a view widget.  Now, it is only  necessary to register an action
with add_actions--model->view mapping is optional.

Signed-off by: David Aguilar <davvid@gmail.com>
2008-01-13 04:21:18 -08:00
David Aguilar b134620872 Use the preferred monospace font in the commit text widget
This makes the commit text widget and the diff display widget use the same,
user-specified, font.

Signed-off by: David Aguilar <davvid@gmail.com>
2008-01-13 04:17:58 -08:00
David Aguilar b543bf3614 Log the failure to import inotify instead of notifying the user with a messagebox
Previously, if python-pyinotify was not installed we would we present the user
with a messagebox informing them of this fact everytime the app is launched.
This changes it so that the nag message is only printed to log only.

Signed-off by: David Aguilar <davvid@gmail.com>
2008-01-13 04:16:37 -08:00
David Aguilar 2f4959f80a Set window sizes and default fonts
If no font is specified for the font combo box then no font will be listed for
the diff font.  Set a selected font so that the field is always populated.
This only affects the case where a user has never previously selected a font.

Signed-off by: David Aguilar <davvid@gmail.com>
2008-01-13 04:13:35 -08:00
David Aguilar f49cfa3453 Change the log window's font colors
Use bold+black for informational: lines.  This makes it easier to see the note versus
the informational text.

Signed-off by: David Aguilar <davvid@gmail.com>
2008-01-13 04:07:38 -08:00
David Aguilar 0d7acc37d3 Never return HEAD as a remote branch name
Listing 'remote/HEAD' as a valid branch is not very useful,
so let's remove it from the gui.

Signed-off by: David Aguilar <davvid@gmail.com>
2008-01-09 04:55:35 -08:00
David Aguilar 51534988df Removed line-wrapping from the commit text field
Removed the line-wrapping so that it's obvious that the commit
text editor is WYSIWYG wrt commit messages.

Signed-off by: David Aguilar <davvid@gmail.com>
2008-01-09 04:45:48 -08:00
David Aguilar 843a468742 Add a gui for displaying arbitrary commits/blobs/trees
Added Repository->Show Revision

This menu item lets you search for revisions using a text input.  The first revision matching the query string is displayed in the gui.  If the sha1 doesn't match any commits, we look for a blob or tree that matches the sha1 and display it.

Signed-off by: David Aguilar <davvid@gmail.com>
2008-01-09 04:30:59 -08:00
David Aguilar 4c1e003497 use \n instead of os.linesep (unix wins the crlf battle)
After the crlf fervor dies down we can add support for whatever crazy scheme is needed on windows.  until then, unix wins (and so does \n).

Signed-off by: David Aguilar <davvid@gmail.com>
2008-01-09 04:13:00 -08:00
David Aguilar 56f529b028 Make the gui friendlier to tiny displays/resolutions.
Without a minimum size set qt wouldn't allow the diff display to size itself down too far.

Signed-off by: David Aguilar <davvid@gmail.com>
2008-01-06 04:01:21 -08:00
Marco Costalba 8dd5033daf Add a missing space in 'inotify support' message
Signed-off by: Marco Costalba <mcostalba@gmail.com>
2008-01-05 03:42:46 -08:00
David Aguilar eebdf4a3c6 Tweak the log output colors, add a window title
Changed the info:message color to be blue:green
Ensured that the command output window is titled.

Signed-off by: David Aguilar <davvid@gmail.com>
2008-01-04 04:28:18 -08:00
David Aguilar dc1f431728 Change the qobserver's notification order. This fixes a bug and removes code made unnecessary by this design.
We now change the disabling of model notification in when setting Qt widget params instead of when setting model params.  This undoes a previous fix that was added to the options controller that would refresh the historybrowser line in the options gui.  That code is no longer needed with this change.  An added benefit is that the interactive font updating is fixed/enabled with this fix.

Signed-off by: David Aguilar <davvid@gmail.com>
2008-01-04 04:16:45 -08:00
David Aguilar 0009e8ecd3 Prevent users from selecting "HEAD" as a remote branch target.
We should never push to HEAD, so prevent selecting it in the push gui.

Signed-off by: David Aguilar <davvid@gmail.com>
2008-01-04 04:12:25 -08:00
David Aguilar 6ecc99d4c2 Add a read-only browse commits option and set window titles for export patch and cherry pick guis.
Previously all of the commit-browsing guis (export patch/cherry-pick) would have the title of "git commit browser".  The title is now set to the current action.

Signed-off by: David Aguilar <davvid@gmail.com>
2008-01-04 03:31:50 -08:00
David Aguilar 9449ff8185 Fix layout for maximized windows
We now set minimum sizes for many widgets so that the gui can be sized smaller than before (if desired).
Marco noticed some layout issues when maximized.  These were fixed by changing the size policies for the widgets.  Overall, the design is better in that widgets are being used  to contain layouts instead of using nested layouts.

Signed-off by: David Aguilar <davvid@gmail.com>
2008-01-04 03:28:24 -08:00
David Aguilar 03e8c1f68e Make the historybrowser config setting show on restart
Renamed the historybrowser config name to match the other config names.
Added the historybrowser parameter to the allowed list so that it is serialized into git config.
Added the historybrowser param to the refreshview method of the optionscontroller so that it is properly read on startup.

Signed-off by: David Aguilar <davvid@gmail.com>
2008-01-03 04:37:34 -08:00
Marco Costalba 34661f0550 Allow user to choose history browser tool
Instead of hard linking gitk let the user set
 the preferred history visualization tool in settings dialog.

Signed-off by:  Marco Costalba <mcostalba@gmail.com>
2008-01-03 04:27:12 -08:00
David Aguilar c69f7ac51f Better/more logging
More commands report information in the log window now.
Disabled whitespace checks in the commit browser.  They're always turned on in the diff gui, which is where it really matters.

Signed-off by: David Aguilar <davvid@gmail.com>
2008-01-03 04:10:01 -08:00
David Aguilar 3130210f09 Log window improvements
Added onthefly search to the logwindow
Better maingui ui for the splitters
Another unittest

Signed-off by: David Aguilar <davvid@gmail.com>
2008-01-03 03:15:50 -08:00
David Aguilar e466fa79e6 Updated release scripts to account for the new homepage
Signed-off by: David Aguilar <davvid@gmail.com>
2008-01-03 01:09:20 -08:00
David Aguilar 99a9dabd96 Make the OutputGUI's log method always put the cursor at the end of the document
We previously made it so that the OutputGUI's log method always sets the cursor at the beginning of the document.  This isn't that nice since the side-effect is that the document gets scrolled to the end.  Let's keep the cursor at the end so that users will see the end of the log (which is where the most recent/useful information is found).

Signed-off by: David Aguilar <davvid@gmail.com>
2008-01-03 01:07:31 -08:00
David Aguilar 342ca06050 Added a log output window for use anywhere where output is presented to the user.
Squashed commit of the following:

commit 06f4dc70817f9178ca4086c0ee52c9fee41852bb
Author: David Aguilar <davvid@gmail.com>
Date:   Thu Jan 3 01:00:11 2008 -0800

    Fixed some corner cases in the log find functionality

    When logging output the cursor defaults to the end of the document.  This made the search method hit the end of the document before ever trying any searches.  When doing OutputGUI().log(..) we now reset the cursor to the beginning of the document.

commit 17858c1367fe8ce9e1760ec7055edbd128db210a
Author: David Aguilar <davvid@gmail.com>
Date:   Thu Jan 3 00:35:44 2008 -0800

    Add a syntax highlighter for the log window

    * Generalized the syntax highlighting logic into a GenericSyntaxHighlighter class.

    * Refactored the DiffSyntaxHighlighter to use the new generic highlighter as a base class.

    * Added a new LogSyntaxHighlighter which is also based on GenericSyntaxHighlighter.

commit 41c6a104bbaa9d5eb4f556692c66e40307346179
Author: David Aguilar <davvid@gmail.com>
Date:   Wed Jan 2 21:42:20 2008 -0800

    Removed line wrapping in the diff display

commit 1f72566562c90170667b43ef8d2a67bdbd8eb77a
Author: David Aguilar <davvid@gmail.com>
Date:   Wed Jan 2 21:30:12 2008 -0800

    Added an output log window

commit 8bedf5b90b9e3fcf8a06a97e526c22f8456f498c
Author: David Aguilar <davvid@gmail.com>
Date:   Wed Jan 2 19:00:36 2008 -0800

    Updated README

commit 108744ec87a197749d478c7a4bd62a05d7e79095
Author: David Aguilar <davvid@gmail.com>
Date:   Wed Jan 2 05:48:05 2008 -0800

    Added a first draft of the logging window

commit d5c78d616b6f9656a7df063a6bd97477ae5d76c5
Author: David Aguilar <davvid@gmail.com>
Date:   Wed Jan 2 05:07:58 2008 -0800

    Renamed GUI attributes so that everything follows a names_with_underscores style.

commit 713a88a125ebb84b607675ff1c3840d691812f65
Author: David Aguilar <davvid@gmail.com>
Date:   Wed Jan 2 00:54:44 2008 -0800

    Ensure the toolbar defaults to the bottom of the screen, but allow nesting at the top

commit 2e905b798ef251f4aef30cb957c61fceb3e1537f
Author: David Aguilar <davvid@gmail.com>
Date:   Wed Jan 2 00:29:15 2008 -0800

    Removed the statusbar, replaced with toolbar for the log window

Signed-off by: David Aguilar <davvid@gmail.com>
2008-01-03 01:02:04 -08:00
David Aguilar 4c248fb616 Mac OS X UI tweaks
Signed-off by: David Aguilar <davvid@gmail.com>
2008-01-01 09:10:12 -08:00
David Aguilar b96cc16811 removed model.clone(init=False) hack
Signed-off by: David Aguilar <davvid@gmail.com>
2008-01-01 09:01:07 -08:00
David Aguilar 7952b401e0 Added option edit/save support via git config.
Squashed commit of the following:

commit ed32c76a8fb4164d93198503c791ff3ed56850a4
Author: David Aguilar <davvid@gmail.com>
Date:   Tue Jan 1 08:28:22 2008 -0800

    Remove model.clone(init=False) hack

commit 2c3518bd55648c23ea86d1165945f4cd398b6049
Author: David Aguilar <davvid@gmail.com>
Date:   Tue Jan 1 08:17:37 2008 -0800

    Use the preferred diff font in the export patch gui as well

commit caf530dac6ad625b9f74ff34bedfae7cc0925f9e
Author: David Aguilar <davvid@gmail.com>
Date:   Tue Jan 1 08:17:06 2008 -0800

    make the repo browser look a little nicer

commit 4e14d807e8e50af8326cea67c15490435b734887
Author: David Aguilar <davvid@gmail.com>
Date:   Tue Jan 1 08:16:35 2008 -0800

    Improve the syntax highlighter

commit 499d03d78ade84f37705fe60232c39e069054893
Author: David Aguilar <davvid@gmail.com>
Date:   Tue Jan 1 07:49:51 2008 -0800

    Implemented an option save/restore interface to git config

commit ca7c0fc78b54fa05f18ee0c90fdba0d8cf8f081f
Author: David Aguilar <davvid@gmail.com>
Date:   Mon Dec 31 22:40:05 2007 -0800

    More config system updates
2008-01-01 08:32:02 -08:00
David Aguilar 8f452cb1f7 Added interactive font selection and update 2007-12-31 21:49:36 -08:00
David Aguilar 16d606baa3 QFont handling for the qobserver class 2007-12-31 20:37:08 -08:00
David Aguilar 49f433209e core improvements for config handling 2007-12-31 19:22:29 -08:00
David Aguilar a39223afb7 i18n for the options dialog 2007-12-31 15:12:27 -08:00
David Aguilar 87ef0c5732 Model improvements for config load/save 2007-12-31 06:36:26 -08:00
David Aguilar 713b5eef40 Remove some unused data and small fixups 2007-12-31 06:19:44 -08:00
David Aguilar 40ca7b3caa Update model classes and add a model testcase 2007-12-31 04:17:32 -08:00
David Aguilar f2023d0cb5 Simplified inotify 2007-12-31 04:10:18 -08:00
David Aguilar 8fe35787d4 Revamp the models for config reading 2007-12-31 01:29:21 -08:00
David Aguilar fa2820c4f6 Rename a couple of controller methods 2007-12-31 01:28:45 -08:00
David Aguilar 7f004fac1b Fixed inotify so that all events are dispatched back to the main thread via QCoreApplication.postEvent() 2007-12-30 22:13:36 -08:00
David Aguilar 0927008f84 Make most of the guis modal
All of the guis (except for the repo browser) are now application-modal.
2007-12-30 21:34:09 -08:00
David Aguilar eeae5d3db7 Update ugit version in the makefile 2007-12-30 21:31:59 -08:00
David Aguilar bc1b89d3c2 Add itemclicked callback to the push and createbranch controllers 2007-12-30 21:31:31 -08:00
David Aguilar 52def8364a Avoid raising an exception when a child cannot be started. 2007-12-30 21:24:01 -08:00
David Aguilar 4b6f10231e Accept a nicer syntax on the callee side for qobserver.add_callbacks
Add_callbacks originally required a dictionary as its input.  Now, it uses **kwargs as its dictionary so that the key=value pairs can be specified directly.
2007-12-30 21:23:11 -08:00
David Aguilar 6f8a6a6c5c Start separating global and local config settings 2007-12-30 21:05:25 -08:00
David Aguilar c84b05142d Finished the option gui 2007-12-30 20:52:12 -08:00
David Aguilar fd99b7389f Changed the create_new_branch method's signature to be consistent
The calling order was changed to create_new_branch(model,view) which is consistent with the other gui helper methods.
2007-12-30 20:50:09 -08:00
David Aguilar e9c3989017 Prep for options menu
Renamed some gui classes so that all gui classes have a GUI suffix.  e.g. PushGUI.
Added a stub options menu
Enabled options menu callback
2007-12-30 20:48:16 -08:00
David Aguilar 5a36ec6752 nice ui tweak 2007-12-30 16:46:08 -08:00
David Aguilar e5c8f2df82 build fix 2007-12-30 15:28:18 -08:00
David Aguilar 98c52056e5 patch export enhancements for merge commits
Selecting a merge commit in the patch export gui will now export all of the merge's individual commits.  Selecting any of the individual commits exports just that patch.
We get this by always passing format-patch a rev .. range expression.

Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-30 14:35:38 -08:00
David Aguilar 66c92acb3d Make the commit browser properly handle merge commits
The commit browser was not properly checking if a commit was a merge.  In those cases, it would not display the entire contents of the commit.  A new method was added to handle this case and hooked into the utilcontroller.

Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-30 12:36:43 -08:00
David Aguilar 992e810a47 Refactored several methods out of the main controller.
Added a utilcontroller module to own simple dialogs such as choose_branch
Moved select_commits into utilcontroller
Moved browse_git_branch into the repobrowsercontroller
Removed lots of view classes from the controller module
Sped up the push dialog's init time by asking the config system for the remote url
Speed up cloning by passing constructor args to model.clone()

Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-29 18:20:41 -08:00
David Aguilar ea83cae50b Refactor methods to allow more delegation to the model and view
Removed all dependencies on non-model modules from the controllers.
Removed usage of python's commands module.
Moved several model-centric methods into the model.
Added some helpers to the view classes.
Pushed most of the diff analysis/processing into utils.DiffParser.

Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-29 07:57:04 -08:00
David Aguilar 9506089576 rename cmds to git, refactor git calls
Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-29 07:03:12 -08:00
David Aguilar a29151bc47 Speed up push/createbranch dialog startup time by re-using existing data
Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-29 03:21:04 -08:00
David Aguilar 7a71feea5a Renamed classes to remove unnecessary prefix.
Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-29 02:57:42 -08:00
David Aguilar 6a0f030249 Use os.listdir intead of ls
Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-28 20:42:00 -08:00
David Aguilar 02c1ecd6ae Syntax sugar
Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-28 20:41:12 -08:00
David Aguilar c16dfea4a9 Properly handle merge commits in the export/cherry-pick guis
Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-28 18:56:26 -08:00
David Aguilar d3ee251795 Rescan when calling the unstage_all action
Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-28 18:36:30 -08:00
David Aguilar fa332572dc Reworked cmds to use cmds.git()
Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-28 18:35:39 -08:00
David Aguilar 8cf09aa29f Exclude a line from inclusion in a diff if the selection only contains its trailing newline.
Reworked cmds.git_diff to make filename an optional parameter.
Changed controllers to pass filename to git_diff
Removed some unused vars from the controller's process_diff_selection().
Changed utils.DiffParser's logic so that including the previous line's newline in the selection will not include that line.

Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-28 18:34:46 -08:00
David Aguilar a7b25a7e9d UI cleanup in the push dialog
i18n cleanups and reordered the UI to minimize mouse clicks

Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-28 16:08:30 -08:00
David Aguilar a5abccd2af Make unstaging a silent operation.
Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-28 14:04:53 -08:00
David Aguilar 01efa2d79e oops, forgot to call model.apply_diff during selected diff staging. 2007-12-27 23:57:05 -08:00
David Aguilar 3bb791f320 Simplified controllers by using qtutils.set_items/add_items
Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-27 18:54:02 -08:00
David Aguilar 478e8d6b2c i18n updates
Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-27 18:52:58 -08:00
David Aguilar 165213bd16 Update win32 build script for documentation
Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-27 18:30:28 -08:00
David Aguilar b584a72a2c Removed dead diff-gui code
Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-27 07:15:09 -08:00
David Aguilar cbc29c7771 Fixed win32 build script
Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-27 07:06:21 -08:00
David Aguilar 7b01aa042e Add fine-grained patch/hunk selection
With this integration, it is now possible to select sub-hunks in the diff gui.
It is now possible to select individual lines and stage those lines only.
To allow per-line selections use the "Stage/Unstage selected lines" context menus.

Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-27 06:57:19 -08:00
David Aguilar 3117a54441 Keep the staged/unstaged lists vertically aligned
Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-27 06:54:53 -08:00
David Aguilar 3e895e4ca1 Renamed the splitter callbacks
Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-27 00:22:31 -08:00
David Aguilar 6415d98a24 Add ability to unstage specific patch hunks
TODO: allow per-line (versus per-hunk) selections

Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-27 00:22:16 -08:00
David Aguilar 1100800e7f Removed unecessary UI items
This commit enforces that ugit will only ever commit what is already staged (and thus in the stagedList).  Partial commits are not allowed, since that prevents partial commits.

Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-27 00:20:08 -08:00
David Aguilar 7c81d3b9fa fork+exec when launching standalone apps
Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-27 00:15:59 -08:00
David Aguilar 0eeeba63cf A few more i18n fixes
Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-26 20:46:55 -08:00
David Aguilar ea861628d4 More i18n tweaks
Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-26 20:11:06 -08:00
David Aguilar 2e64634ba5 Guard against inotify floods (such as when running make or switching branches)
Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-26 19:47:54 -08:00
David Aguilar af3d5d7163 Removed dead code from the syntax highlighter
Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-26 19:27:28 -08:00
David Aguilar 48a3dfacfa Updated build scripts to match the latest/greatest waf api
Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-26 19:19:30 -08:00
David Aguilar b04d83347e Default to ffwd updates only in the push dialog
Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-26 16:59:17 -08:00
David Aguilar a4f6903655 More i18n support
Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-26 16:56:24 -08:00
David Aguilar 9317d78fda Simplified actions by sending along widgets of interest
When a model's parameters change, and the system knows about a corresponding view widget, the mediator's action callback will now receive a reference to the view's widget when the action is called.  This simplifies a common pattern in the action methods.

Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-26 16:55:48 -08:00
David Aguilar 1507c4e51f More i18n goodness
Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-26 06:37:18 -08:00
David Aguilar 51c0065075 Updated TODO 2007-12-26 05:11:14 -08:00
David Aguilar be397d3354 Updated win32 build script to accomodate translation files
Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-26 04:37:51 -08:00
David Aguilar 6e6230c28d Add internationalization support to ugit
Renamed a few UI items to align with git-gui's existing translations.
Added the QApplication calls to setup the translator.
Started using self.tr() in the controllers.
2007-12-26 04:24:01 -08:00
David Aguilar 038198f41a Updated default colors for pyuic4/po2qm build objects
Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-26 04:17:39 -08:00
David Aguilar 9083f05d61 Prepared ugit for i18n support
Imported git-gui's .po translation files.
Added a po2qm build object

Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-26 01:23:57 -08:00
David Aguilar 681518f151 Updated READMEs
Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-25 19:02:40 -08:00
David Aguilar 4408e0ae77 Don't include .pyc/.pyo files in released tarballs
Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-25 04:31:41 -08:00
David Aguilar 56b674f283 Refactored model/view classes to pass around less data
commit 50f517616a5e0b3391911a9f8dcb5037a1ecf4c7
Author: David Aguilar <davvid@gmail.com>
Date:   Mon Dec 24 09:25:48 2007 -0800

    Updated TODO to reflect latest updates

commit 0fcfd488d56f8918e18768988d7bddcd6bc3e1b5
Author: David Aguilar <davvid@gmail.com>
Date:   Mon Dec 24 09:11:31 2007 -0800

    Updated ugit controllers for the model/view refactoring

commit ef847b22072db6c2103a902d53e5852b71cef2f1
Author: David Aguilar <davvid@gmail.com>
Date:   Mon Dec 24 09:09:58 2007 -0800

    Refactored model/view classes to avoid passing around the model

commit 86dbbb74334bfc8371461d2304ad7281ff56bd8c
Author: David Aguilar <davvid@gmail.com>
Date:   Mon Dec 24 09:07:19 2007 -0800

    Simplify utils.grep's usage and implementation

commit 13cfb22f286124a4cb18eb8f1c04bef5c317668c
Author: David Aguilar <davvid@gmail.com>
Date:   Mon Dec 24 09:02:28 2007 -0800

    Simplify view imports

Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-24 09:29:19 -08:00
David Aguilar acdb8d3120 Allow pushing tags
Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-24 05:26:42 -08:00
David Aguilar 8ea60c2317 Added a push dialog and a some UI niceties
Squashed commit of the following:

commit 1f5a6e4c6b4dd47500a2a26c50334b8d1f57997b
Author: David Aguilar <davvid@gmail.com>
Date:   Mon Dec 24 05:10:15 2007 -0800

Store splitter geometry so that settings are remembered across sessions

commit b0d249887a76fb01d14d5e241b5b0bbb895feaed
Author: David Aguilar <davvid@gmail.com>
Date:   Mon Dec 24 04:46:57 2007 -0800

Separated out the info display from the diff display

commit 59660cec1b28b1a0703092974f3303a05befba70
Author: David Aguilar <davvid@gmail.com>
Date:   Mon Dec 24 04:25:45 2007 -0800

Added proper cut/copy/paste/delete/undo/redo for the commit text

commit f16f7fa212e88829ce88a749d07e9dc116c2a728
Author: David Aguilar <davvid@gmail.com>
Date:   Mon Dec 24 04:25:16 2007 -0800

Removed unused fetch+rebase menu action (TODO later)

commit 2a05bb7b66175fd14592789a513a4d735097fb0f
Author: David Aguilar <davvid@gmail.com>
Date:   Mon Dec 24 04:01:49 2007 -0800

Show the remote URL in the push dialog

commit 30c59974cb5a5305eb7a4ef1291ddc3df9333d13
Author: David Aguilar <davvid@gmail.com>
Date:   Mon Dec 24 04:01:05 2007 -0800

Added a convenient grep function

commit bf7a9144632d9ed4a61edb2ce6cb2115b85a8230
Author: David Aguilar <davvid@gmail.com>
Date:   Mon Dec 24 04:00:14 2007 -0800

Correctly handle temp build files when creating tarballs

commit c5bf345be573a1e6d851a22e1bab5bb5682512bd
Author: David Aguilar <davvid@gmail.com>
Date:   Mon Dec 24 03:38:31 2007 -0800

Make ugit remember window geometry settings

Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-24 05:15:18 -08:00
David Aguilar 03d7f6feb9 Added a "git push" dialog
commit 84353b6fd7afeeda44a8233611286b6d2b71ca88
Author: David Aguilar <davvid@gmail.com>
Date:   Sat Dec 22 04:43:52 2007 -0800

    Added a Git Push dialog

commit bd69f7975dc3662042099af8e3d89ee8741cc683
Author: David Aguilar <davvid@gmail.com>
Date:   Fri Dec 21 23:49:46 2007 -0800

    Display project/branch in titlebar

Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-22 04:45:17 -08:00
David Aguilar 1a7ad1b99b Added commit message menu, other fixes
commit 4e80caa2b8d06571640b1351956e60118194460b
Author: David Aguilar <davvid@gmail.com>
Date:   Fri Dec 21 23:20:17 2007 -0800

    Load commit message menu

commit 7aed0d0e110f699bfd02316c641eb5baaca39b15
Author: David Aguilar <davvid@gmail.com>
Date:   Fri Dec 21 22:53:23 2007 -0800

    Cleanup model names

commit bd123a0dcb8a63a72c683b4dc70a010156cb64fb
Author: David Aguilar <davvid@gmail.com>
Date:   Fri Dec 21 22:53:06 2007 -0800

    Dir dialog

commit b4fe4ff48f66660a3952c06c4fce144d39e3a6e6
Author: David Aguilar <davvid@gmail.com>
Date:   Fri Dec 21 22:13:52 2007 -0800

    fix for mktar script

commit 1c85af2ab70f76a7523e5c4ac7a670e34f1df3e6
Author: David Aguilar <davvid@gmail.com>
Date:   Fri Dec 21 22:04:41 2007 -0800

    Build dir shuffle

commit ea12a220cb33124eb2c2ed57353a5e2582046705
Author: David Aguilar <davvid@gmail.com>
Date:   Fri Dec 21 21:51:33 2007 -0800

    style cleanup

commit 6b71609035b35f0a23a9d6c492def3757e2ebb1e
Author: David Aguilar <davvid@gmail.com>
Date:   Fri Dec 21 21:15:01 2007 -0800

    style cleanup

commit bb3b90d53ee2d166356c11a87f63d2ea8adcf219
Author: David Aguilar <davvid@gmail.com>
Date:   Fri Dec 21 20:53:42 2007 -0800

    Removed a few lambda's

commit c883104cda9ade3644c5ff5162c2996ab338feeb
Author: David Aguilar <davvid@gmail.com>
Date:   Fri Dec 21 20:47:46 2007 -0800

    Less output for common tasks, simplified callbacks

commit 9f75684588fcf92e33a97dd8182e957114bc3b07
Author: David Aguilar <davvid@gmail.com>
Date:   Fri Dec 21 20:12:02 2007 -0800

    script for creating tarballs

Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-21 23:23:30 -08:00
David Aguilar 4144137d41 Added interactive diff gui
Right-clicking on the diff display now allows you to stage selected hunks from an unstaged diff.  This was one of the main pieces of functionality missing from git-gui that is now supported by ugit.

Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-21 05:47:54 -08:00
David Aguilar bdfb60d4ba Added diff parser for use by the interactive diff gui
Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-21 03:37:00 -08:00
David Aguilar 6f0d02ab65 Show whitespace errors in the diff display
This mimicks what command-line git does by highlighting extraneous whitespace with a red background.

Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-21 03:09:12 -08:00
David Aguilar aa44509af2 Move model-centric rescan logic into the model class
Simplified a few things and moved most of the rescan logic into the main model class.

Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-21 02:25:14 -08:00
David Aguilar 0dfbfcc671 Allow launching multiple copies of the repo browser
Use a cloned model when browsing the repo browser.  This lets you several modeless repobrowsers, each viewing a different branch.

Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-21 02:06:56 -08:00
David Aguilar fa4b9d848d Consolidated model classes into a single class
All views now share the same model.
Added obligatory qobserver destructor to handle unregistering observers from their subjects.

Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-21 01:57:21 -08:00
David Aguilar b4a399f465 Change default window sizes to accomodate courier font
Updated the main window to display a visible cursor
Allow selection with the keyboard in the diff textedit
Ensured that utils.header() is always 80 columns wide

Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-21 01:55:37 -08:00
David Aguilar 552662eb96 Make things look nicer on MacOS
Tweaked the layout a bit
Removed the shell_quote stuff from run_cmd (since QProcess doesn't need it)
Make the "export file" functionality not rely on shell redirection (this should help when running on win32)

Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-19 23:38:32 -08:00
David Aguilar 1a375df3fa Better Win32 support
We now support msysgit.  The environment is still kind of frankenstein
since we're using Python (native win32) + PyQt4 (native win32) + Git (msys), but somehow most of it still works.

Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-19 02:47:14 -08:00
David Aguilar e8beb56f30 Win32 README, build scripts
Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-17 02:25:50 -08:00
David Aguilar dbca023099 Fixes to allow win32 packages
Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-17 02:05:25 -08:00
David Aguilar 06730ad424 Updated scripts for win32-style installation 2007-12-17 01:24:14 -08:00
David Aguilar d6fa62438c Ensure run_cmd also gets stderr, cleaned up usage of shell_quote
Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-16 18:02:41 -08:00
David Aguilar d1c922bd75 Explicitly catch ImportErrors only when importing inotify
Change the import test to try importiny inotify from ugitlibs instead of the system's pyinotify module.  Only catch ImportErrors to avoid catching real mistakes.  If we're on linux, recommend installing the python-inotify module.

Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-16 15:45:50 -08:00
David Aguilar 6afbf39187 Moved the inotify import after the try/except import of pyinotify
Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-16 13:40:20 -08:00
David Aguilar d1a2392211 Merge branch 'for-david' of git://repo.or.cz/ugit/ugit-sp into next 2007-12-16 13:36:48 -08:00
David Aguilar 0d679d92b7 gitignores 2007-12-16 13:36:39 -08:00
Steffen Prohaska a5d90073d6 cmds: Include command in return value of git_add()
Signed-off-by: Steffen Prohaska <prohaska@zib.de>
2007-12-16 17:33:54 +01:00
Steffen Prohaska d96f9eb2bb cmds: Fix spurios IOErrors by using QProcess to run commands
Python's commands module sometimes raises IOErrors, which may be related to
signal handlers set by Qt.  It is likely that Qt manipulates Unix signals,
though I did not find a reference clearly stating this.  Certain signals,
notably signals related to child process handling, may cause read calls to
be interrupted.  Such an interruption seems to be transformed into an
IOError by Python's commands module.

The spurious errors are know to occure on Mac OS X with
    Python 2.3.5 (#1, Jan 13 2006, 20:13:11)
    [GCC 4.0.1 (Apple Computer, Inc. build 5250)] on darwin

Using QProcess as a replacement fixes the error.  Qt knows about its own
signal handler manipulations.  The spurious errors are gone.

Signed-off-by: Steffen Prohaska <prohaska@zib.de>
2007-12-16 17:32:30 +01:00
Steffen Prohaska 0dbdfcec43 cmds: Refactor commands.getoutput to run_cmd()
Direct calls to commands.getoutput() are wrapped in the function
run_cmd().

This is in preparation for replacing commands.getoutput with
QProcess to avoid spurious IOErrors.

Signed-off-by: Steffen Prohaska <prohaska@zib.de>
2007-12-16 16:41:34 +01:00
Steffen Prohaska 60dab63b96 controllers: Remove global import of inotify
Not all systems support inotify.  Hence, it should not be imported
globally, but only if needed.

Signed-off-by: Steffen Prohaska <prohaska@zib.de>
2007-12-16 16:41:34 +01:00
David Aguilar d9b5136e7f Optimize the diff display by using QSyntaxHighlighter
Squashed commit of the following:

commit 469d388327218fa015de74b9e3ec99933847e501
Author: David Aguilar <davvid@gmail.com>
Date:   Sat Dec 15 06:01:46 2007 -0800

    Vastly improved the diff/repo display

    Added GitSyntaxHighlighter
    Changed dialogs to use the GitSyntaxHighlighter
    Removed all HTML-related junk

    Signed-off by: David Aguilar <davvid@gmail.com>

commit 7d8a52e25f5049a0d14c20e9009bd283c1452cff
Author: David Aguilar <davvid@gmail.com>
Date:   Sat Dec 15 04:15:15 2007 -0800

    Context menu
2007-12-15 06:03:47 -08:00
David Aguilar c40fd604af Make ugit not require PYTHONPATH when installed to random prefixes
Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-15 04:08:29 -08:00
David Aguilar 271d4d1caf Updated README, .gitignore for ugit-patches
Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-15 04:07:45 -08:00
David Aguilar 29927d41f3 Updated README and added scripts/just-build-it.sh for the impatient.
Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-14 02:00:49 -08:00
David Aguilar 8ac7284b5c shell_quote bugfix
Oops!  We were using rstrip()/lstrip() to remove repeated single-quotes when really we should have just taken a substring.

Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-13 02:07:57 -08:00
David Aguilar 14c68f4032 Speed up the diff display by using cStringIO
Previously we were joining large chunks of text using temporary arrays.
Using a cStringIO object noticably faster.

Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-13 02:01:48 -08:00
David Aguilar 36a0c83812 Removed superfluous action
Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-12 22:01:20 -08:00
David Aguilar 30dfcfc569 Merged inotify branch into master: added inotify support
Squashed commit of the following:

commit 3ca04bff83504d4aa9cc14ee7511d121679b2f12
Author: David Aguilar <davvid@gmail.com>
Date:   Wed Dec 12 21:33:38 2007 -0800

    Removed the rescan button since there's no use for it anymore

    Removed the rescan button.  The Commit -> Rescan action still exists for those without inotify.  Ctrl-R, as always, is still wired to the rescan action as well.

    Signed-off by: David Aguilar <davvid@gmail.com>

commit 50a14f03229a6f2d4c8b8536fc4d246481f4a104
Author: David Aguilar <davvid@gmail.com>
Date:   Wed Dec 12 21:30:17 2007 -0800

    Added inotify support

    Creating, deleting, moving or modifying files known to git now triggers
    inotify events.  ugit watches these events and rescans for changes
    automatically.

    Signed-off by: David Aguilar <davvid@gmail.com>

commit a675fbdfa87ad72d8803843e39f00505850191cf
Author: David Aguilar <davvid@gmail.com>
Date:   Wed Dec 12 09:47:12 2007 -0800

    ugit-notify.py for interfacing with inotify
2007-12-12 21:36:25 -08:00
David Aguilar 158ba54885 TODO notes
Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-12 09:41:44 -08:00
David Aguilar d676ffce26 checkout branch menu items
Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-12 01:58:45 -08:00
David Aguilar f42b503ae3 ... 2007-12-12 00:50:07 -08:00
David Aguilar e8e8e5d30b Signed-off by: David Aguilar <davvid@gmail.com> 2007-12-12 00:16:13 -08:00
David Aguilar f8d101cc76 Steffen Prohaska <prohaska@zib.de> 2007-12-11 23:29:17 -08:00
David Aguilar e0589d6fa8 Merged branchmenu into master
Squashed commit of the following:

commit d163fe6a8e22f3d7a2fab643fe1c0940c03a9b86
Author: David Aguilar <davvid@gmail.com>
Date:   Tue Dec 11 05:12:09 2007 -0800

    Creating branches works, but some options are not implemented

    The fastforward and fetch options are not implemented, but creating local and remote-based branches now works.

    Signed-off by: David Aguilar <davvid@gmail.com>

commit b2d4215f92d6b1341c7f267c7b38174d47eea743
Author: David Aguilar <davvid@gmail.com>
Date:   Tue Dec 11 04:41:13 2007 -0800

    UI updates: changed hotkeys

    Signed-off by: David Aguilar <davvid@gmail.com>

commit 8213aeb641257e9493a97802790f742126d5714b
Author: David Aguilar <davvid@gmail.com>
Date:   Tue Dec 11 03:50:36 2007 -0800

    Don't use os.path.basename for remote branches -> local branch name translation since that won't work on win32

    model and controller now use os.sep.  I don't have a windows installation on which to test this, but it /should/ work there. YMMV.

    Signed-off by: David Aguilar <davvid@gmail.com>

commit 9de5409b6f3dd603b9f5cf5689acf09f6aec54a0
Author: David Aguilar <davvid@gmail.com>
Date:   Tue Dec 11 03:44:00 2007 -0800

    Added copyright notices

    ugit is gplv2+.  Reflect that in the README.

    Signed-off by: David Aguilar <davvid@gmail.com>

commit 0413b053d9fcf9317be85ba75ba249330b30f6b5
Author: David Aguilar <davvid@gmail.com>
Date:   Tue Dec 11 01:51:14 2007 -0800

    Use more screen real-estate

    Most screens these days are at least 1024x768.  Let's use a little more of that and give more prominence to the diff/content display.

    Signed-off by: David Aguilar <davvid@gmail.com>

commit 5cb6e0d9f1f7bebe220c0818932bc69aedfe0e25
Author: David Aguilar <davvid@gmail.com>
Date:   Tue Dec 11 00:12:00 2007 -0800

    Improved GitCreateBranch interaction behavior.

    Renamed some model attributes.
    Branch name auto-setting now only happens when interacting with remote branches.

    Signed-off by: David Aguilar <davvid@gmail.com>

commit ed46dd0d7e7164feb287e60aec6c82bfd38feb33
Author: David Aguilar <davvid@gmail.com>
Date:   Mon Dec 10 14:43:26 2007 -0800

    Create branch WIP

    Signed-off by: David Aguilar <davvid@gmail.com>

commit cb9be7cf07239696f83b5855e360df78fbd43917
Author: David Aguilar <davvid@gmail.com>
Date:   Mon Dec 10 14:16:38 2007 -0800

    Removed superfluous *args from most callbacks

    Callbacks that respond to parameter-less signals all had a superfluous *args argument that was never being used.
    With the exception of GitController's cb_rescan, these were all
    removed.  cb_rescan needs it because it also responds to the
    untracked checkbox's stateChanged(int) signal which passes in
    the current state of the checkbox.

    Signed-off by: David Aguilar <davvid@gmail.com>

commit 8e5388ad7af5d5a4b930dee5ae9933e7d3801da0
Author: David Aguilar <davvid@gmail.com>
Date:   Mon Dec 10 14:05:39 2007 -0800

    Simplified GitCreateBranchController.__get_branch_sources()

    Removed superfluous method and moved logic checks inline.
    Added a stub version of cb_create_branch.

    Signed-off by: David Aguilar <davvid@gmail.com>

commit 2928637b09707cfef48541c0bc1e2a03ddce60b1
Author: David Aguilar <davvid@gmail.com>
Date:   Mon Dec 10 13:58:19 2007 -0800

    Updated GitCreateBranchController with user-interaction callbacks

    Interacting with the branchRootList will now update the model's revision_id and branch_name values.  The branch_name value is not updated when tags are selected.  When updated, the branch_name value is run through os.path.basename to translate "origin/foo" to just "foo."

    Signed-off by: David Aguilar <davvid@gmail.com>

commit 74321561172014ee5b1beb3784c481ae7d18949f
Author: David Aguilar <davvid@gmail.com>
Date:   Mon Dec 10 04:18:17 2007 -0800

    Added the first version of the GitCreateBranch dialog

    - Added CreateBranchDialog.ui
    - Added subclass in views
    - Updated models with GitCreateBranchModel
    - Moved a few model-centric methods from the controllers to the models

    Signed-off by: David Aguilar <davvid@gmail.com>

commit 6234d93bb2f3089dc6f62d4dc3698822129e96e7
Author: David Aguilar <davvid@gmail.com>
Date:   Mon Dec 10 01:32:59 2007 -0800

    Moved the __set_branch_ui_items() call into cb_rescan

    Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-11 05:16:09 -08:00
David Aguilar 0067cca128 Merge branch 'master' of david@pixies:/shared/pub/ugit 2007-12-11 05:14:51 -08:00
David Aguilar 103766573c Added TODO
Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-11 05:13:21 -08:00
David Aguilar 8bbb074657 Updated tooltips
Edited Window.ui's tooltips to use multiple lines for easier readability.

Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-10 01:53:50 -08:00
David Aguilar 14325ef551 Updated build scripts to follow coding standards
Fixed a few style nitpicks to make the wscripts use the same conventions as the rest of ugit.

Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-10 01:41:52 -08:00
David Aguilar 394ffee414 qobserver subclasses do not need to import SIGNAL
- Updated qobserver to override connect() and so that subclasses can pass in the signal string only and thus do not have to import QtCore.SIGNAL.  Passing in a QtCore.SIGNAL is still supported.

Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-09 14:30:05 -08:00
David Aguilar 942367df3e README updates
- Updated the README to contain a standard copyright notice

Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-09 13:46:54 -08:00
David Aguilar 0fed8c03a2 Updated q/observer to follow coding conventions
- Fixed a few small style nitpicks in q/observer so that they are consistent with the rest of ugit

Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-09 13:46:24 -08:00
David Aguilar 62c2ca14d5 Added observer.py in preparation for the first ugit public release
- observer.py was originally part of a different (private) repo so I've moved it in here for public consumption

Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-09 13:24:59 -08:00
David Aguilar ae843c8aad Added support for renamed file detection in cmds.git_status()
- cmds.git_status now includes a regex for renamed files
- renamed files are listed twice in the staged list: once for the deleted file and once for the new filename
- cmds.git_status's other regexes were also fixed to not require the filename to be lstrip()'d

Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-09 13:22:19 -08:00
David Aguilar 2e7366c86d Added waf script and configure wrapper in preparation for public release
Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-09 12:48:42 -08:00
David Aguilar fbb1929a7c Build scripts cleanup
Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-09 04:26:10 -08:00
David Aguilar d342135c14 Added buildutils and model/observer modules
- These were previously part of another (private) repo but they should live here if this is going to be consumed by the public.

Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-09 04:06:17 -08:00
David Aguilar b90ab11119 Merged repobrowser into master and added SQUASH_MSG importing
- Added support for importing the SQUASH_MSG from a merge-in-progress.

Squashed commit of the following:

commit 610c3765ad7fbfbdeb82c21d02be10d0ce5126c5
Author: David Aguilar <davvid@gmail.com>
Date:   Sun Dec 9 03:11:17 2007 -0800

    Implemented patch export in the repo browser

    - Added a save-as dialog in the repocontroller's double-click action
    - Added a module for holding session-lifetime default values

    Signed-off by: David Aguilar <davvid@gmail.com>

commit 5e86d35aa5c17095d9c58130a62b7a4e6dfb6e1a
Author: David Aguilar <davvid@gmail.com>
Date:   Sun Dec 9 02:12:18 2007 -0800

    Performance improvements for ansi_to_html

    - Avoid creating spurious string copies
    - Avoid superfluous int->str conversion
    - Reworked html_encode to not use a temporary ANSI array
    - Reordered html_encode's checks to catch the common case first

    Signed-off by: David Aguilar <davvid@gmail.com>

commit 2ec40f9ef38d525efa95ded41e0d2c8001528e31
Author: David Aguilar <davvid@gmail.com>
Date:   Sun Dec 9 00:31:21 2007 -0800

    Added tooltips and changed default window sizes

    - Changed default window sizes so that the diff, command, and content display would be 80 columns wide
    - Added tooltips to all items in the main ugit interface (Window.ui)

    Signed-off by: David Aguilar <davvid@gmail.com>

commit caa924eecd18b9667f5e07a5d3aa74a278db3fc1
Author: David Aguilar <davvid@gmail.com>
Date:   Sun Dec 9 00:29:22 2007 -0800

    Fixed a bug where "Commit All" was commiting unstaged changes

    - Removed the "commit_all" parameter from git_commit in cmds
    - Changed the controller to pass model.get_staged() when "Commit All" is checked

    Signed-off by: David Aguilar <davvid@gmail.com>

commit d3d0486738993591feb7fa0976cbd5e6038a4745
Author: David Aguilar <davvid@gmail.com>
Date:   Sun Dec 9 00:18:50 2007 -0800

    Use git cat-file to show file contents

    - Added git_cat_file in cmds
    - Updated repobrowsercontroller to use git_cat_file
    - Added item_types to GitRepoBrowserModel
    - Renamed GitRepoBrowserModel's items to item_names for consistency

    Signed-off by: David Aguilar <davvid@gmail.com>

commit a77aeb2b57d2e2aa490fcf145c3220d968aecb58
Author: David Aguilar <davvid@gmail.com>
Date:   Sat Dec 8 04:26:59 2007 -0800

    Added "browse other branch" functionality

    - Added git_branch_list to cmd
    - Created GitBranchDialog / BranchDialog.ui
    - Added invocation of the GitBranchDialog in cb_branch_other
    - Guarded against item() returning None in repobrowsercontroller's cb_item_changed
    - Changed the commit browser's label to say "Object ID" instead of "Revision Expression" since that is more generic

    Signed-off by: David Aguilar <davvid@gmail.com>

commit 7aa54406410e57b2102704a7774fd8e8ac0ef136
Author: David Aguilar <davvid@gmail.com>
Date:   Sat Dec 8 03:50:14 2007 -0800

    Fixed a bug when changing directories

    When changing into a directory with fewer items than the current directory the cb_item_changed callback is triggered.  When it's triggered the listwidget's current property still points at the old value of "current," thus we have an array index that is out of range.  We now guard against overstepping the item_sha1s array.

    Signed-off by: David Aguilar <davvid@gmail.com>

commit a547d323cb45573d996d7d59de7602388eb9998b
Author: David Aguilar <davvid@gmail.com>
Date:   Sat Dec 8 03:47:01 2007 -0800

    Removed redundant label from CommandDialog

    Signed-off by: David Aguilar <davvid@gmail.com>

commit 10d610ed61b73188b7d39f210f28849d62ccfd99
Author: David Aguilar <davvid@gmail.com>
Date:   Fri Dec 7 20:29:44 2007 -0800

    Implemented the GitRepoBrowser

    - Added qtutils.py for qt utility methods
    - Added git_ls_tree to cmds.py
    - Added dir.png icon
    - Renamed a few modules for consistency
    - Added separate model and controller for the repobrowser
    - The CommitBrowser was reused as the RepoBrowser (same view)

    Signed-off by: David Aguilar <davvid@gmail.com>

Commit signed-off by: David Aguilar <davvid@gmail.com>
2007-12-09 03:33:51 -08:00
David Aguilar 90dc4111dc Added the GPL3 license, updated README
ugit is distributed under the GPL v3

Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-09 03:14:07 -08:00
David Aguilar b77cffd178 Finalized api rename
- Renamed all modules from ugit* to just *
- All libs are now under ugitlibs

Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-07 15:53:27 -08:00
David Aguilar 7a381c92d9 Renamed files in preparation for api changes
- We're going to put all ugit modules under ugit.$(module)
- This removes the need for api/ugitapi.py
- This also allows for nicer filenames
- The next commit updates the class names and adds __init__.py
2007-12-07 15:31:15 -08:00
103 changed files with 21859 additions and 2543 deletions
+16 -3
View File
@@ -1,4 +1,17 @@
/build
/install
/.lock-wscript
*.deb
*.pyc
*.pyo
*.rpm
*.swp
*~
/debian/files
/debian/bld
/debian/cola
/installroot
/.lock-wscript
/obj
/Makefile
/meta
/patches
/releases
/tmp
+165
View File
@@ -0,0 +1,165 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.
+88 -163
View File
@@ -1,196 +1,121 @@
ugit: ugit's git-gui's inner truffles
git-cola: a highly caffeinated git gui
ugit started out as a python/pyqt re-implementation of git-gui.
It has an evolving feature set and aims to be a fully-featured
companion to the existing git uis.
Cola is a sweet, carbonated git gui known for its
sugary flavour and caffeine-inspired features.
DEPENDENCIES:
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
python 2.4.x
pyqt 4.3.x
pyqtmvc 1.1.x
Copyright (C) 2008 David Aguilar
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
REQUIREMENTS
Python 2.4 or newer
http://python.org/
PyQt 4.3 or newer
http://www.riverbankcomputing.co.uk/software/pyqt/intro
simplejson
http://pypi.python.org/pypi/simplejson
ABOUT THE ICONS:
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
ACKNOWLEDGEMENTS
staged.png was blatanly stolen from the "slick" KDE icon set.
git-cola includes a copy of the GitPython module:
http://gitorious.org/projects/git-python/
All other icons blatanly stolen from Apache's icons:
These icons are covered by the following copyright license:
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
INSTALLATION
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
Debian and RPM users can download freshly built binaries here:
1. Definitions.
http://cola.tuxfamily.org/
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
sudo dpkg -i cola_*.deb
sudo rpm -ivh cola-*.rpm
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
If you downloaded a tarball, make sure /path/to/cola/bin is in your $PATH.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
HOW TO RUN
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
Take your pick--any of the following incantations work from the command line:
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
git cola
git-cola
cola
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
Launching cola on repositories outside your $CWD:
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
git cola /repo/path-1 /repo/path-2 .. /repo/path-N
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
BUILDING FROM SOURCE
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
You will need the PyQt development tools (v4.3 or greater):
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
Synopsis:
sudo apt-get install pyqt4-dev-tools
git clone git://gitorious.org/git-cola/mainline.git cola
cd cola
./configure --prefix=/usr
make
sudo make install
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
WINDOWS INSTALLATION
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
1) Install the native Python2.5 and PyQt4 installers (in that order):
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
http://python.org/download/
http://www.riverbankcomputing.co.uk/pyqt/download.php
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
3) Install msysgit:
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
http://git.or.cz/gitwiki/WindowsInstall
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
4) Install the GnuWin32 'file' command:
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
http://gnuwin32.sourceforge.net/packages/file.htm
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
Add the installed directory (default: c:\GnuWin32\bin) to your path.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
5) Install simplejson
http://pypi.python.org/pypi/simplejson
6) Download cola:
http://cola.tuxfamily.org/releases/
7) Install cola (from a Git Bash):
cd /c/Python25/Lib/site-packages
tar xvfz /path/to/cola-win32-*.tar.gz
mv cola-win32-* cola
8) Create an executable in your path for bash, cmd.exe, or both:
echo '/c/Python25/python.exe /c/Python25/Lib/site-packages/cola/bin/git-cola' >> /usr/bin/cola
echo 'c:\\Python25\\python.exe c:\\Python25\\Lib\\site-packages\\bin\\git-cola' > /c/Windows/System32/cola.bat
This assumes the you have Python/PyQt4 installed in C:/Python25.
If this is not the case simply edit the commands accordingly.
voila, cola on Windows.
NOTE: Windows is not the developer's primary platform.
Patches are greatly appreciated.
-4
View File
@@ -1,4 +0,0 @@
#!/usr/bin/env python
import os
import sys
sys.path.append (os.path.join (os.path.dirname (__file__), 'ugit'))
-14
View File
@@ -1,14 +0,0 @@
#!/usr/bin/env python
import sys
from PyQt4 import QtCore, QtGui
import ugitapi
from ugitmodel import GitModel
from ugitview import GitView
from ugitcontroller import GitController
if __name__ == "__main__":
app = QtGui.QApplication (sys.argv)
model = GitModel()
view = GitView (app.activeWindow())
ctl = GitController (model, view)
view.show()
sys.exit(app.exec_())
+1
View File
@@ -0,0 +1 @@
/.waf-*
+59
View File
@@ -0,0 +1,59 @@
#!/usr/bin/env python
import os
import Action
import Object
import Params
import Utils
import Common
"Converts .po files to the Qt .qm format"
NAME='po2qm'
class po2qm_obj(Object.genobj):
s_default_ext = ['.po']
def __init__(self):
Object.genobj.__init__(self, 'other')
self.inst_var = 'QMDIR'
self.inst_dir = ''
self.chmod = 0644
def apply(self):
# create the nodes corresponding to the sources
for filename in self.to_list(self.source):
base, ext = os.path.splitext(filename)
if not ext in self.s_default_ext:
Params.fatal("unknown file " + filename)
split_filename = Utils.split_path(filename)
node = self.path.find_source_lst(split_filename)
task = self.create_task(NAME, self.env, 101)
task.set_inputs(node)
task.set_outputs(node.change_ext('.qm'))
def install(self):
for i in self.m_tasks:
current = Params.g_build.m_curdirnode
lst=[a.relpath_gen(current) for a in i.m_outputs]
Common.install_files(self.inst_var,
self.inst_dir,
lst,
chmod=self.chmod)
def setup(env):
Object.register(NAME, po2qm_obj)
Action.simple_action(NAME, '${MSGFMT} ${MSGFMT_FLAGS} -o ${TGT} ${SRC}', color='YELLOW')
def detect(conf):
if not conf.find_program('msgfmt', var='MSGFMT'):
Params.fatal('Error: missing msgfmt executable.')
return False
conf.env['MSGFMT_FLAGS'] = '--qt'
conf.env['MSGFMT_EXT'] = ['.po']
conf.env['QMDIR'] = os.path.join(conf.env['PREFIX'],
'share',
Utils.g_module.APPNAME,
'qm')
return True
+106
View File
@@ -0,0 +1,106 @@
#!/usr/bin/env python
# Author: David Aguilar
"""
pyuic4: support for generating .py Python scripts from Qt Designer4 .ui files.
NOTES:
- If PYQT4_ROOT is given(absolute path), the configuration will look
in PYQT4_ROOT/bin first.
- This module hooks adds a python hook that runs
a pyuic4 action when '.ui' files are encoutnered.
"""
import os
import Action
import Object
import Params
import python
python.pyobj.s_default_ext.append('.ui')
def set_options(opt):
"""Adds the --pyuic4 build option."""
opt.add_option('--pyuic4',
type='string',
dest='pyuic4',
default='',
help='path to pyuic4')
def create_pyuic4_tasks(self, node):
"""Creates the tasks to generate python files.
The "pyuic4" action is called for this."""
# Create a pyuic4 task to generate the python .py file
pyuic4task = self.create_task('pyuic4')
pyuic4task.set_inputs(node)
pyuic4task.set_outputs(node.change_ext('.py'))
# Add the python compilation tasks
if self.pyc:
task = self.create_task('pyc', self.env, 50)
task.set_inputs(node.change_ext('.py'))
task.set_outputs(node.change_ext('.pyc'))
if self.pyo:
task = self.create_task('pyo', self.env, 50)
task.set_inputs(node.change_ext('.py'))
task.set_outputs(node.change_ext('.pyo'))
def setup(env):
"""Creates a python hook and registers it with the environment."""
# create the hook action
cmd_template = '${PYUIC4} ${PYUIC4_FLAGS} ${SRC} -o ${TGT}'
Action.simple_action('pyuic4', cmd_template, 'GREEN')
# register .ui for use with python
Object.hook('py', 'PYUIC4_EXT', create_pyuic4_tasks)
def detect(conf):
env = conf.env
opt = Params.g_options
pyuic4 = None
try:
pyuic4 = opt.pyuic4
except:
pass
if not pyuic4:
qtdir = os.environ.get('PYQT4_ROOT', '')
if qtdir:
binpath = [qtdir] + os.environ['PATH'].split(':')
else:
binpath = os.environ['PATH'].split(':')
for f in ['pyuic4', 'pyuic-qt4', 'pyuic']:
pyuic4 = conf.find_program(f, path_list=binpath)
if pyuic4:
break
if not pyuic4:
conf.check_message('pyuic4 binary', '(not found)', 0)
Params.fatal('Error: missing PyQt4 development tools.')
return False
# Set the path to pyuic4
env['PYUIC4'] = pyuic4
env['PYUIC4_EXT'] = ['.ui']
env['PYUIC4_FLAGS'] = '-x'
vercmd = env['PYUIC4'] + ' --version 2>&1'
version = os.popen(vercmd).read().strip().split(' ')[-1]
version = version.split('.')[0]
if not version.isdigit() or int(version) < 4:
conf.check_message('pyuic4 version', '(not found or too old)', 0,
option= '(%s)' % version)
return False
conf.check_message('pyuic4 version', '', 1, option='(%s)' % version)
# all tests passed
return True
Vendored Executable
+314
View File
File diff suppressed because one or more lines are too long
View File
+877
View File
@@ -0,0 +1,877 @@
#!/usr/bin/env python
import os
import sys
import time
import glob
import platform
from PyQt4 import QtCore
from PyQt4 import QtGui
from PyQt4.QtGui import QDialog
from PyQt4.QtGui import QMessageBox
from PyQt4.QtGui import QMenu
from PyQt4.QtGui import QFont
from cola import utils
from cola import qtutils
from cola import defaults
from cola.qobserver import QObserver
# controllers namespace
import search
from util import logger
from remote import remote_action
from util import choose_from_list
from util import choose_from_combo
from util import select_commits
from util import update_options
from repobrowser import browse_git_branch
from createbranch import create_new_branch
from search import search_commits
from merge import local_merge
from merge import abort_merge
from bookmark import save_bookmark
from bookmark import manage_bookmarks
from stash import stash
class Controller(QObserver):
"""Manages the interaction between models and views."""
MODE_NONE = 0
MODE_WORKTREE = 1
MODE_INDEX = 2
MODE_BRANCH = 3
def init(self, model, view):
"""
State machine:
Modes are:
none -> do nothing, disables most context menus
branch -> diff against another branch and selectively choose changes
worktree -> selectively add working changes to the index
index -> selectively remove changes from the index
"""
self.mode = Controller.MODE_NONE
# parent-less log window
qtutils.LOGGER = logger()
# Unstaged changes context menu
view.unstaged.contextMenuEvent = self.unstaged_context_menu_event
# Diff display context menu
view.display_text.contextMenuEvent = self.diff_context_menu_event
# Binds model params to their equivalent view widget
self.add_observables('commitmsg', 'staged', 'unstaged')
# When a model attribute changes, this runs a specific action
self.add_actions(staged = self.action_staged)
self.add_actions(unstaged = self.action_unstaged)
self.add_actions(global_cola_fontdiff = self.update_diff_font)
self.add_actions(global_cola_fontui = self.update_ui_font)
self.add_callbacks(
# Push Buttons
signoff_button = self.model.add_signoff,
stage_button = self.stage_selected,
commit_button = self.commit,
fetch_button = self.fetch,
push_button = self.push,
pull_button = self.pull,
# List Widgets
staged = self.diff_staged,
unstaged = self.diff_unstaged,
# Checkboxes
untracked_checkbox = self.rescan,
# File Menu
menu_quit = self.quit_app,
menu_open_repo = self.open_repo,
menu_manage_bookmarks = manage_bookmarks,
menu_save_bookmark = save_bookmark,
# Edit Menu
menu_options = self.options,
menu_cut = self.view.action_cut,
menu_copy = self.view.action_copy,
menu_paste = self.view.action_paste,
menu_delete = self.view.action_delete,
menu_select_all = self.view.action_select_all,
menu_undo = self.view.action_undo,
menu_redo = self.view.action_redo,
# Search Menu
menu_search_grep = self.grep,
menu_search_revision =
self.gen_search( search.REVISION_ID ),
menu_search_revision_range =
self.gen_search( search.REVISION_RANGE ),
menu_search_message =
self.gen_search( search.MESSAGE ),
menu_search_path =
self.gen_search( search.PATH, True ),
menu_search_date_range =
self.gen_search( search.DATE_RANGE ),
menu_search_diff =
self.gen_search( search.DIFF ),
menu_search_author =
self.gen_search( search.AUTHOR ),
menu_search_committer =
self.gen_search( search.COMMITTER ),
# Merge Menu
menu_merge_local =
lambda: local_merge( self.model, self.view ),
menu_merge_abort =
lambda: abort_merge( self.model, self.view ),
# Repository Menu
menu_visualize_current = self.viz_current,
menu_visualize_all = self.viz_all,
menu_browse_commits = self.browse_commits,
menu_browse_branch = self.browse_current,
menu_browse_other_branch = self.browse_other,
# Branch Menu
menu_create_branch = self.branch_create,
menu_checkout_branch = self.checkout_branch,
menu_diff_branch = self.diff_branch,
menu_diffedit_branch = self.diffedit_branch,
# Commit Menu
menu_rescan = self.rescan,
menu_delete_branch = self.branch_delete,
menu_rebase_branch = self.rebase,
menu_commit = self.commit,
menu_stage_selected = self.stage_selected,
menu_unstage_selected = self.unstage_selected,
menu_show_diffstat = self.show_diffstat,
menu_show_index = self.show_index,
menu_export_patches = self.export_patches,
menu_stash =
lambda: stash( self.model, self.view ),
menu_load_commitmsg = self.load_commitmsg,
menu_cherry_pick = self.cherry_pick,
menu_get_prev_commitmsg = model.get_prev_commitmsg,
menu_stage_modified =
lambda: self.log(self.model.stage_modified()),
menu_stage_untracked =
lambda: self.log(self.model.stage_untracked()),
menu_unstage_all =
lambda: self.log(self.model.unstage_all()),
)
# Delegate window events here
view.moveEvent = self.move_event
view.resizeEvent = self.resize_event
view.closeEvent = self.quit_app
view.staged.mousePressEvent = self.click_staged
view.unstaged.mousePressEvent = self.click_unstaged
# These are vanilla signal/slots since QObserver
# is already handling these signals.
self.connect(view.unstaged,
'itemDoubleClicked(QListWidgetItem*)',
self.stage_selected)
self.connect(view.staged,
'itemDoubleClicked(QListWidgetItem*)',
self.unstage_selected)
# Toolbar log button
self.connect(view.toolbar_show_log,
'triggered()', self.show_log)
self.connect(view.diff_dock,
'topLevelChanged(bool)',
lambda(b): self.setwindow(view.diff_dock, b))
self.connect(view.editor_dock,
'topLevelChanged(bool)',
lambda(b): self.setwindow(view.editor_dock, b))
self.connect(view.status_dock,
'topLevelChanged(bool)',
lambda(b): self.setwindow(view.status_dock, b))
self.init_log_window()
self.load_gui_settings()
self.rescan()
self.refresh_view('global_cola_fontdiff', 'global_cola_fontui')
self.start_inotify_thread()
def setwindow(self, dock, isfloating):
if isfloating:
if platform.system() != 'Windows':
flags = ( QtCore.Qt.Window
| QtCore.Qt.FramelessWindowHint )
dock.setWindowFlags( flags )
dock.show()
#####################################################################
# handle when the listitem icons are clicked
def click_event(self, widget, action_callback, event):
result = QtGui.QListWidget.mousePressEvent(widget, event)
xpos = event.pos().x()
if xpos > 5 and xpos < 20:
action_callback()
return result
def click_staged(self, event):
return self.click_event(self.view.staged,
self.unstage_selected, event)
def click_unstaged(self, event):
return self.click_event(self.view.unstaged,
self.stage_selected, event)
#####################################################################
# event() is called in response to messages from the inotify thread
def event(self, msg):
if msg.type() == defaults.INOTIFY_EVENT:
self.rescan()
return True
else:
return False
#####################################################################
# Actions triggered during model updates
def action_staged(self, widget):
qtutils.update_listwidget(widget,
self.model.get_staged(),
staged=True)
self.view.editor_dock.raise_()
def action_unstaged(self, widget):
qtutils.update_listwidget(widget,
self.model.get_modified(),
staged=False)
if self.view.untracked_checkbox.isChecked():
qtutils.update_listwidget(widget,
self.model.get_untracked(),
staged=False,
append=True,
untracked=True)
#####################################################################
# Qt callbacks
def gen_search(self, searchtype, browse=False):
def search_handler():
search_commits(self.model, searchtype, browse)
return search_handler
def grep(self):
txt, ok = qtutils.input("grep")
if not ok:
return
stuff = self.model.grep(txt)
self.view.display_text.setText(stuff)
self.view.diff_dock.raise_()
def show_log(self, *rest):
qtutils.toggle_log_window()
def options(self):
update_options(self.model, self.view)
def branch_create(self):
if create_new_branch(self.model, self.view):
self.rescan()
def branch_delete(self):
branch = choose_from_combo('Delete Branch',
self.view,
self.model.get_local_branches())
if not branch:
return
self.log(self.model.delete_branch(branch))
def browse_current(self):
branch = self.model.get_currentbranch()
browse_git_branch(self.model, self.view, branch)
def browse_other(self):
# Prompt for a branch to browse
branch = choose_from_combo('Browse Branch Files',
self.view,
self.model.get_all_branches())
if not branch:
return
# Launch the repobrowser
browse_git_branch(self.model, self.view, branch)
def checkout_branch(self):
branch = choose_from_combo('Checkout Branch',
self.view,
self.model.get_local_branches())
if not branch:
return
self.log(self.model.checkout(branch))
def browse_commits(self):
self.select_commits_gui(self.tr('Browse Commits'),
*self.model.log_helper(all=True))
def cherry_pick(self):
commits = self.select_commits_gui(self.tr('Cherry-Pick Commits'),
*self.model.log_helper(all=True))
if not commits:
return
self.log(self.model.cherry_pick_list(commits))
def commit(self):
msg = self.model.get_commitmsg()
if not msg:
error_msg = self.tr(""
+ "Please supply a commit message.\n"
+ "\n"
+ "A good commit message has the following format:\n"
+ "\n"
+ "- First line: Describe in one sentence what you did.\n"
+ "- Second line: Blank\n"
+ "- Remaining lines: Describe why this change is good.\n")
self.log(error_msg)
return
files = self.model.get_staged()
if not files and not self.view.amend_radio.isChecked():
error_msg = self.tr(""
+ "No changes to commit.\n"
+ "\n"
+ "You must stage at least 1 file before you can commit.\n")
self.log(error_msg)
return
# Perform the commit
amend = self.view.amend_radio.isChecked()
output = self.model.commit_with_msg(msg, amend=amend)
# Reset state
self.view.new_commit_radio.setChecked(True)
self.view.amend_radio.setChecked(False)
self.model.set_commitmsg('')
self.log(output)
def view_diff(self, staged=True):
if staged:
self.mode = Controller.MODE_INDEX
widget = self.view.staged
else:
self.mode = Controller.MODE_WORKTREE
widget = self.view.unstaged
row, selected = qtutils.get_selected_row(widget)
if not selected:
self.mode = Controller.MODE_NONE
self.view.reset_display()
return
(diff, status) = self.model.get_diff_and_status(row, staged=staged)
self.view.set_display(diff)
self.view.set_info(self.tr(status))
self.view.diff_dock.raise_()
def edit_file(self, staged=True):
if staged:
widget = self.view.staged
else:
widget = self.view.unstaged
row, selected = qtutils.get_selected_row(widget)
if not selected:
return
if staged:
filename = self.model.get_staged()[row]
else:
filename = self.model.get_unstaged()[row]
utils.fork(self.model.get_editor(), filename)
def edit_diff(self, staged=True):
if staged:
widget = self.view.staged
else:
widget = self.view.unstaged
row, selected = qtutils.get_selected_row(widget)
diff, status = self.model.get_diff_and_status(row, staged=staged)
if not selected:
return
if staged:
filename = self.model.get_staged()[row]
else:
filename = self.model.get_unstaged()[row]
contents = self.model.show("HEAD:"+filename, with_raw_output=True)
tmpfile = self.model.get_tmp_filename(filename)
fh = open(tmpfile, 'w')
fh.write(contents)
fh.close()
utils.fork(self.model.get_diffeditor(), filename, tmpfile)
# use *rest to handle being called from different signals
def diff_staged(self, *rest):
self.view_diff(staged=True)
# use *rest to handle being called from different signals
def diff_unstaged(self, *rest):
self.view_diff(staged=False)
def export_patches(self):
(revs, summaries) = self.model.log_helper()
to_export = self.select_commits_gui(self.tr('Export Patches'),
revs, summaries)
if not to_export:
return
to_export.reverse()
revs.reverse()
self.log(self.model.format_patch_helper(to_export,
revs,
output='patches'))
def open_repo(self):
"""Spawns a new cola session"""
dirname = qtutils.opendir_dialog(self.view,
'Open Git Repository...',
os.getcwd())
if dirname:
utils.fork(sys.argv[0], dirname)
def quit_app(self, *args):
"""Save config settings and cleanup any inotify threads."""
if self.model.save_at_exit():
self.model.save_gui_settings()
qtutils.close_log_window()
pattern = self.model.get_tmp_file_pattern()
for filename in glob.glob(pattern):
os.unlink(filename)
if self.inotify_thread and self.inotify_thread.isRunning():
self.inotify_thread.abort = True
self.inotify_thread.wait()
self.view.close()
def load_commitmsg(self):
file = qtutils.open_dialog(self.view,
'Load Commit Message...',
defaults.DIRECTORY)
if file:
defaults.DIRECTORY = os.path.dirname(file)
slushy = utils.slurp(file)
if slushy:
self.model.set_commitmsg(slushy)
def rebase(self):
branch = choose_from_combo('Rebase Branch',
self.view,
self.model.get_local_branches())
if not branch:
return
self.log(self.model.rebase(branch))
# use *rest to handle being called from the checkbox signal
def rescan(self, *rest):
"""Populates view widgets with results from 'git status.'"""
# save entire selection
unstaged = qtutils.get_selection_list(self.view.unstaged,
self.model.get_unstaged())
staged = qtutils.get_selection_list(self.view.staged,
self.model.get_staged())
scrollbar = self.view.display_text.verticalScrollBar()
scrollvalue = scrollbar.value()
# save selected item
unstageditem = qtutils.get_selected_item(self.view.unstaged,
self.model.get_unstaged())
stageditem = qtutils.get_selected_item(self.view.staged,
self.model.get_staged())
# get new values
self.model.update_status()
# restore selection
update_staged = False
update_unstaged = False
updated_unstaged = self.model.get_unstaged()
updated_staged = self.model.get_staged()
for item in unstaged:
if item in updated_unstaged:
idx = updated_unstaged.index(item)
listitem = self.view.unstaged.item(idx)
if listitem:
listitem.setSelected(True)
self.view.unstaged.setItemSelected(listitem, True)
update_unstaged = True
self.view.unstaged.update()
for item in staged:
if item in updated_staged:
idx = updated_staged.index(item)
listitem = self.view.staged.item(idx)
if listitem:
listitem.setSelected(True)
self.view.staged.setItemSelected(listitem, True)
update_staged = True
# restore selected item
if update_unstaged and unstageditem:
idx = updated_unstaged.index(unstageditem)
item = self.view.unstaged.item(idx)
self.view.unstaged.setCurrentItem(item)
self.view_diff(False)
scrollbar.setValue(scrollvalue)
elif update_staged and stageditem:
idx = updated_staged.index(stageditem)
item = self.view.staged.item(idx)
self.view.staged.setCurrentItem(item)
self.view_diff(True)
scrollbar.setValue(scrollvalue)
# Update the title with the current branch
self.view.setWindowTitle('%s [%s]' % (
self.model.get_project(),
self.model.get_currentbranch()))
# Check if there's a message file in .git/
merge_msg_path = self.model.get_merge_message_path()
if merge_msg_path is None:
return
# A merge message file exists.
set_msg = False
if self.model.get_commitmsg():
# The commit message editor contains data.
# Prompt before overwriting the commit message
# with the contents of the merge message.
answer = qtutils.question(self.view,
self.tr('Import Commit Message?'),
self.tr('A commit message from an in-progress'
+' merge was found.\nImport it?'))
if answer:
set_msg = True
else:
set_msg = True
# Set the new commit message
if set_msg:
self.model.load_commitmsg(merge_msg_path)
self.view.editor_dock.raise_()
def fetch(self):
remote_action(self.model, self.view, "Fetch")
def push(self):
remote_action(self.model, self.view, "Push")
def pull(self):
remote_action(self.model, self.view, "Pull")
def show_diffstat(self):
"""Show the diffstat from the latest commit."""
self.mode = Controller.MODE_NONE
self.view.set_info(self.tr('Diffstat'))
self.view.set_display(self.model.diffstat())
def show_index(self):
self.mode = Controller.MODE_NONE
self.view.set_info(self.tr('Index'))
self.view.set_display(self.model.diffindex())
#####################################################################
def diffedit_branch(self):
branch = choose_from_combo('Select Branch',
self.view,
self.model.get_all_branches())
if not branch:
return
zfiles_str = self.model.diff(branch, name_only=True, z=True)
files = zfiles_str.split('\0')
filename = choose_from_list('Select File', self.view, files)
if not filename:
return
contents = self.model.show('%s:%s' % (branch, filename), with_raw_output=True)
tmpfile = self.model.get_tmp_filename(filename)
fh = open(tmpfile, 'w')
fh.write(contents)
fh.close()
utils.fork(self.model.get_diffeditor(), filename, tmpfile)
# Set state machine to branch mode
self.mode = Controller.MODE_NONE
#####################################################################
# diff gui
def diff_branch(self):
branch = choose_from_combo('Select Branch',
self.view,
self.model.get_all_branches())
if not branch:
return
zfiles_str = self.model.diff(branch, name_only=True, z=True)
files = zfiles_str.split('\0')
filename = choose_from_list('Select File', self.view, files)
if not filename:
return
status = ('Diff of "%s" between the work tree and %s'
% (filename, branch))
diff = self.model.diff_helper(filename=filename,
cached=False,
reverse=True,
branch=branch)
self.view.set_display(diff)
self.view.set_info(self.tr(status))
self.view.diff_dock.raise_()
# Set state machine to branch mode
self.mode = Controller.MODE_BRANCH
self.branch = branch
self.filename = filename
def process_diff_selection(self, items, widget,
cached=True, selected=False, reverse=True):
if self.mode == Controller.MODE_BRANCH:
branch = self.branch
filename = self.filename
parser = utils.DiffParser(self.model,
filename=filename,
cached=False,
branch=branch)
offset, selection = self.view.diff_selection()
parser.process_diff_selection(selected, offset, selection,
branch=True)
self.rescan()
else:
filename = qtutils.get_selected_item(widget, items)
if not filename:
return
parser = utils.DiffParser(self.model,
filename=filename,
cached=cached)
offset, selection = self.view.diff_selection()
parser.process_diff_selection(selected, offset, selection)
self.rescan()
def stage_hunk(self):
self.process_diff_selection(self.model.get_unstaged(),
self.view.unstaged,
cached=False)
def stage_hunk_selection(self):
self.process_diff_selection(self.model.get_unstaged(),
self.view.unstaged,
cached=False,
selected=True)
def unstage_hunk(self, cached=True):
self.process_diff_selection(self.model.get_staged(),
self.view.staged,
cached=True)
def unstage_hunk_selection(self):
self.process_diff_selection(self.model.get_staged(),
self.view.staged,
cached=True,
selected=True)
# #######################################################################
# end diff gui
# *rest handles being called from different signals
def stage_selected(self,*rest):
"""Use "git add" to add items to the git index.
This is a thin wrapper around map_to_listwidget."""
command = self.model.add_or_remove
widget = self.view.unstaged
items = self.model.get_unstaged()
self.map_to_listwidget(command, widget, items)
# *rest handles being called from different signals
def unstage_selected(self, *rest):
"""Use "git reset" to remove items from the git index.
This is a thin wrapper around map_to_listwidget."""
command = self.model.reset_helper
widget = self.view.staged
items = self.model.get_staged()
self.map_to_listwidget(command, widget, items)
def undo_changes(self):
"""Reverts local changes back to whatever's in HEAD."""
widget = self.view.unstaged
items = self.model.get_unstaged()
potential_items = qtutils.get_selection_list(widget, items)
items_to_undo = []
untracked = self.model.get_untracked()
for item in potential_items:
if item not in untracked:
items_to_undo.append(item)
if items_to_undo:
if not qtutils.question(self.view,
self.tr('Destroy Local Changes?'),
self.tr('This operation will drop all '
+'uncommitted changes. '
+'Continue?'),
default=False):
return
output = self.model.checkout('HEAD', '--', *items_to_undo)
self.log('git checkout HEAD -- '
+' '.join(items_to_undo)
+'\n' + output)
else:
msg = 'No files selected for checkout from HEAD.'
self.log(self.tr(msg))
def viz_all(self):
"""Visualizes the entire git history using gitk."""
browser = self.model.get_history_browser()
utils.fork(browser,'--all')
def viz_current(self):
"""Visualizes the current branch's history using gitk."""
browser = self.model.get_history_browser()
utils.fork(browser, self.model.get_currentbranch())
def move_event(self, event):
defaults.X = event.pos().x()
defaults.Y = event.pos().y()
def resize_event(self, event):
defaults.WIDTH = event.size().width()
defaults.HEIGHT = event.size().height()
def load_gui_settings(self):
if not self.model.remember_gui_settings():
return
(w,h,x,y,st0,st1,sb0,sb1) = self.model.get_window_geom()
self.view.resize(w,h)
self.view.move(x,y)
def log(self, output, rescan=True, quiet=False):
"""Logs output and optionally rescans for changes."""
qtutils.log(output, quiet=quiet, doraise=False)
if rescan:
self.rescan()
def map_to_listwidget(self, command, widget, items):
"""This is a helper method that retrieves the current
selection list, applies a command to that list,
displays a dialog showing the output of that command,
and calls rescan to pickup changes."""
apply_items = qtutils.get_selection_list(widget, items)
output = command(*apply_items)
self.log(output, quiet=True)
def unstaged_context_menu_event(self, event):
menu = self.unstaged_context_menu_setup()
unstaged = self.view.unstaged
menu.exec_(unstaged.mapToGlobal(event.pos()))
def unstaged_context_menu_setup(self):
unstaged_item = qtutils.get_selected_item(self.view.unstaged,
self.model.get_unstaged())
is_tracked = unstaged_item not in self.model.get_untracked()
enable_staging = self.mode == Controller.MODE_WORKTREE
enable_undo = enable_staging and is_tracked
menu = QMenu(self.view)
if enable_staging:
menu.addAction(self.tr('Stage Selected'), self.stage_selected)
if enable_undo:
menu.addAction(self.tr('Undo Local Changes'), self.undo_changes)
menu.addAction(self.tr('Launch Editor'),
lambda: self.edit_file(staged=False))
if enable_staging:
menu.addAction(self.tr('Launch Diff Editor'),
lambda: self.edit_diff(staged=False))
return menu
def diff_context_menu_event(self, event):
menu = self.diff_context_menu_setup()
textedit = self.view.display_text
menu.exec_(textedit.mapToGlobal(event.pos()))
def diff_context_menu_setup(self):
menu = QMenu(self.view)
if self.mode == Controller.MODE_WORKTREE:
unstaged_item = qtutils.get_selected_item(self.view.unstaged,
self.model.get_unstaged())
is_tracked= (unstaged_item
and unstaged_item not in self.model.get_untracked())
if is_tracked:
menu.addAction(self.tr('Stage Hunk For Commit'),
self.stage_hunk)
menu.addAction(self.tr('Stage Selected Lines'),
self.stage_hunk_selection)
elif self.mode == Controller.MODE_INDEX:
menu.addAction(self.tr('Unstage Hunk From Commit'),
self.unstage_hunk)
menu.addAction(self.tr('Unstage Selected Lines'),
self.unstage_hunk_selection)
elif self.mode == Controller.MODE_BRANCH:
menu.addAction(self.tr('Apply Diff To Work Tree'),
self.stage_hunk)
menu.addAction(self.tr('Apply Diff Selection To Work Tree'),
self.stage_hunk_selection)
menu.addAction(self.tr('Copy'),
self.view.copy_display)
return menu
def select_commits_gui(self, title, revs, summaries):
return select_commits(self.model, self.view, title, revs, summaries)
def update_diff_font(self):
font = self.model.get_global_cola_fontdiff()
if not font:
return
qfont = QFont()
qfont.fromString(font)
self.view.display_text.setFont(qfont)
self.view.commitmsg.setFont(qfont)
def update_ui_font(self):
font = self.model.get_global_cola_fontui()
if not font:
return
qfont = QFont()
qfont.fromString(font)
QtGui.qApp.setFont(qfont)
def init_log_window(self):
branch = self.model.get_currentbranch()
version = defaults.VERSION
qtutils.log(self.model.get_git_version()
+'\ncola version '+ version
+'\nCurrent Branch: '+ branch)
def start_inotify_thread(self):
# Do we have inotify? If not, return.
# Recommend installing inotify if we're on Linux.
self.inotify_thread = None
try:
from cola.inotify import GitNotifier
qtutils.log(self.tr('inotify support: enabled'))
except ImportError:
import platform
if platform.system() == 'Linux':
msg = self.tr('inotify: disabled\n'
'Note: To enable inotify, '
'install python-pyinotify.\n')
plat = platform.platform().lower()
if 'debian' in plat or 'ubuntu' in plat:
msg += self.tr('On Debian or Ubuntu systems, '
'try: sudo apt-get install '
'python-pyinotify')
qtutils.log(msg)
return
# Start the notification thread
self.inotify_thread = GitNotifier(self, os.getcwd())
self.inotify_thread.start()
+51
View File
@@ -0,0 +1,51 @@
#!/usr/bin/env python
import os
import sys
from PyQt4 import QtGui
from cola import utils
from cola import qtutils
from cola.qobserver import QObserver
from cola.settings import SettingsModel
from cola.views import BookmarkView
def save_bookmark():
model = SettingsModel()
bookmark = os.getcwd()
if bookmark not in model.bookmarks:
model.add_bookmarks(bookmark)
model.save_all_settings()
qtutils.information("Bookmark Saved")
def manage_bookmarks():
model = SettingsModel()
view = BookmarkView(QtGui.qApp.activeWindow())
ctl = BookmarkController(model, view)
view.show()
if view.exec_() == QtGui.QDialog.Accepted:
model.save_all_settings()
class BookmarkController(QObserver):
def init(self, model, view):
self.add_observables( 'bookmarks' )
self.add_callbacks(button_open = self.open,
button_delete = self.delete)
self.refresh_view()
def open(self):
selection = qtutils.get_selection_list(self.view.bookmarks,
self.model.bookmarks)
if not selection:
return
for item in selection:
utils.fork("git", "cola", item)
def delete(self):
selection = qtutils.get_selection_list(self.view.bookmarks,
self.model.bookmarks)
if not selection:
return
for item in selection:
self.model.bookmarks.remove(item)
self.refresh_view()
+126
View File
@@ -0,0 +1,126 @@
#!/usr/bin/env python
import os
from PyQt4.QtGui import QDialog
from cola import utils
from cola import qtutils
from cola.views import CreateBranchView
from cola.qobserver import QObserver
def create_new_branch(model,parent):
model = model.clone()
view = CreateBranchView(parent)
ctl = CreateBranchController(model, view)
view.show()
return view.exec_() == QDialog.Accepted
class CreateBranchController(QObserver):
def init(self, model, view):
self.add_observables('revision', 'local_branch')
self.add_callbacks(branch_list = self.item_changed,
create_button = self.create_branch,
local_radio = self.display_model,
remote_radio = self.display_model,
tag_radio = self.display_model)
self.display_model()
#+--------------------------------------------------------------------
#+ Qt callbacks
def create_branch(self):
"""This callback is called when the "Create Branch"
button is called."""
revision = self.model.get_revision()
branch = self.model.get_local_branch()
existing_branches = self.model.get_local_branches()
if not branch or not revision:
qtutils.information(
self.tr('Missing Data'),
self.tr('Please provide both a branch'
+' name and revision expression.'))
return
check_branch = False
if branch in existing_branches:
if self.view.no_update_radio.isChecked():
msg = self.tr("Branch '%s' already exists.")
msg = unicode(msg) % branch
qtutils.information( self.tr('warning'), msg )
return
# Whether we should prompt the user for lost commits
commits = self.model.rev_list_range(revision, branch)
check_branch = bool(commits)
if check_branch:
msg = self.tr("Resetting '%s' to '%s' will "
"lose the following commits:")
lines = [ unicode(msg) % (branch, revision) ]
for idx, commit in enumerate(commits):
subject = commit[1][0:min(len(commit[1]),16)]
if len(subject) < len(commit[1]):
subject += '...'
lines.append('\t' + commit[0][:8]
+'\t' + subject)
if idx >= 5:
skip = len(commits) - 5
lines.append('\t(%d skipped)' % skip)
break
lines.extend([
unicode(self.tr('Recovering lost commits may not be easy.')),
unicode(self.tr("Reset '%s'?")) % branch
])
result = qtutils.question(self.view, self.tr('warning'),
'\n'.join(lines))
if not result:
return
track = self.view.remote_radio.isChecked()
fetch = self.view.fetch_checkbox.isChecked()
ffwd = self.view.ffwd_only_radio.isChecked()
reset = self.view.reset_radio.isChecked()
output = self.model.create_branch(branch, revision, track=track)
qtutils.show_output(output)
self.view.accept()
def item_changed(self, *rest):
"""This callback is called when the item selection changes
in the branch_list."""
qlist = self.view.branch_list
( row, selected ) = qtutils.get_selected_row(qlist)
if not selected: return
sources = self.__get_branch_sources()
rev = sources[row]
# Update the model with the selection
self.model.set_revision(rev)
# Only set the branch name field if we're
# branching from a remote branch.
if not self.view.remote_radio.isChecked():
return
branch = utils.basename(rev)
if branch == 'HEAD':
return
self.model.set_local_branch(branch)
######################################################################
def display_model(self):
branches = self.__get_branch_sources()
qtutils.set_items(self.view.branch_list, branches)
def __get_branch_sources(self):
"""Get the list of items for populating the branch root list."""
if self.view.local_radio.isChecked():
return self.model.get_local_branches()
elif self.view.remote_radio.isChecked():
return self.model.get_remote_branches()
elif self.view.tag_radio.isChecked():
return self.model.get_tags()
+82
View File
@@ -0,0 +1,82 @@
#!/usr/bin/env python
from cola import utils
from cola import qtutils
from cola.qobserver import QObserver
from cola.views import MergeView
def abort_merge(model, parent):
txt = parent.tr('Abort merge?\n'
'Aborting the current merge will cause '
'*ALL* uncommitted changes to be lost.\n\n'
'Continue with aborting the current merge?')
answer = qtutils.question(parent, parent.tr('Abort Merge?'), txt)
if answer:
model.abort_merge()
def local_merge(model, parent):
model = model.clone()
model.create(revision='', revision_list=[])
view = MergeView(parent)
ctl = MergeController(model, view)
view.show()
class MergeController(QObserver):
def init(self, model, view):
# Set the current branch label
branch = self.model.get_currentbranch()
title = unicode(self.tr('Merge Into %s')) % branch
self.view.label.setText(title)
self.add_observables('revision', 'revision_list')
self.add_callbacks(radio_local = self.radio_callback,
radio_remote = self.radio_callback,
radio_tag = self.radio_callback,
revision_list = self.revision_selected,
button_viz = self.viz_revision,
button_merge = self.merge_revision)
self.model.set_revision_list(self.model.get_local_branches())
self.view.radio_local.setChecked(True)
def get_revision_list(self):
if self.view.radio_local.isChecked():
return self.model.get_local_branches()
elif self.view.radio_remote.isChecked():
return self.model.get_remote_branches()
elif self.view.radio_tag.isChecked():
return self.model.get_tags()
return []
def revision_selected(self, *args):
revlist = self.get_revision_list()
widget = self.view.revision_list
row, selected = qtutils.get_selected_row(widget)
if selected and row < len(revlist):
revision = revlist[row]
self.model.set_revision(revision)
def radio_callback(self):
revlist = self.get_revision_list()
self.model.set_revision_list(revlist)
def merge_revision(self):
revision = self.model.get_revision()
if not revision:
qtutils.information('No Revision Specified',
'You must specify a revision to merge')
return
no_commit = not(self.view.checkbox_commit.isChecked())
squash = self.view.checkbox_squash.isChecked()
msg = self.model.get_merge_message()
qtutils.log(self.model.merge('-m'+msg,
revision,
strategy='recursive',
no_commit=no_commit,
squash=squash),
quiet=False,
doraise=True)
self.view.accept()
def viz_revision(self):
revision = self.model.get_revision()
browser = self.model.get_history_browser()
utils.fork(browser, revision)
+131
View File
@@ -0,0 +1,131 @@
import os
from PyQt4.QtGui import QDialog
from cola import utils
from cola import qtutils
from cola.views import RemoteView
from cola.qobserver import QObserver
def remote_action(model, parent, action):
model = model.clone()
model.create(remotename='',
tags_checkbox=False,
ffwd_only_checkbox=True)
if action == "Fetch":
model.set_tags_checkbox(True)
view = RemoteView(parent, action)
controller = RemoteController(model, view, action)
view.show()
return view.exec_() == QDialog.Accepted
class RemoteController(QObserver):
def init(self, model, view, action):
self.add_observables('remotename',
'remotes',
'local_branch',
'local_branches',
'remote_branch',
'remote_branches',
'tags_checkbox',
'ffwd_only_checkbox')
self.action_method = {
"Fetch": self.fetch_action,
"Push": self.push_action,
"Pull": self.pull_action,
}[action]
self.add_actions(remotes = self.display_remotes)
self.add_callbacks(action_button = self.action_method,
remotes = self.update_remotes,
local_branches = self.update_local_branches,
remote_branches = self.update_remote_branches)
self.refresh_view()
if self.view.select_first_remote():
self.model.set_remotename(self.model.get_remotes()[0])
def display_remotes(self, widget):
displayed = []
for remotename in self.model.get_remotes():
url = self.model.remote_url(remotename)
display = ('%s\t(%s %s)'
% (remotename, unicode(self.tr('URL:')), url))
displayed.append(display)
qtutils.set_items(widget,displayed)
def update_remotes(self,*rest):
widget = self.view.remotes
remotes = self.model.get_remotes()
selection = qtutils.get_selected_item(widget, remotes)
if not selection:
return
self.model.set_remotename(selection)
self.view.remotename.selectAll()
def update_local_branches(self,*rest):
branches = self.model.get_local_branches()
widget = self.view.local_branches
selection = qtutils.get_selected_item(widget, branches)
if not selection:
return
self.model.set_local_branch(selection)
self.model.set_remote_branch(selection)
self.view.local_branch.selectAll()
self.view.remote_branch.selectAll()
def update_remote_branches(self,*rest):
widget = self.view.remote_branches
branches = self.model.get_remote_branches()
selection = qtutils.get_selected_item(widget,branches)
if not selection:
return
branch = utils.basename(selection)
if branch == 'HEAD':
return
self.model.set_remote_branch(branch)
self.view.remote_branch.selectAll()
def check_remote(self):
if not self.model.get_remotename():
errmsg = self.tr('No repository selected.')
qtutils.show_output(errmsg)
return False
else:
return True
def get_common_args(self):
return ((self.model.get_remotename(),),
{
"local_branch": self.model.get_local_branch(),
"remote_branch": self.model.get_remote_branch(),
"ffwd": self.model.get_ffwd_only_checkbox(),
"tags": self.model.get_tags_checkbox(),
})
def show_results(self, status, output):
qtutils.show_output(output)
if not status:
self.view.accept()
else:
qtutils.raise_logger()
#+-------------------------------------------------------------
#+ Actions
def fetch_action(self):
if not self.check_remote():
return
args, kwargs = self.get_common_args()
self.show_results(*self.model.fetch_helper(*args, **kwargs))
def pull_action(self):
if not self.check_remote():
return
args, kwargs = self.get_common_args()
self.show_results(*self.model.pull_helper(*args, **kwargs))
def push_action(self):
if not self.check_remote():
return
args, kwargs = self.get_common_args()
self.show_results(*self.model.push_helper(*args, **kwargs))
+149
View File
@@ -0,0 +1,149 @@
#!/usr/bin/env python
import os
from PyQt4.QtGui import QDialog
from cola import utils
from cola import qtutils
from cola import defaults
from cola.views import CommitView
from cola.qobserver import QObserver
def browse_git_branch(model, parent, branch):
if not branch:
return
# Clone the model to allow opening multiple browsers
# with different sets of data
model = model.clone()
model.set_currentbranch(branch)
view = CommitView(parent)
controller = RepoBrowserController(model, view)
view.show()
return view.exec_() == QDialog.Accepted
class RepoBrowserController(QObserver):
def init(self, model, view):
view.setWindowTitle('File Browser')
self.add_signals('itemSelectionChanged()', view.commit_list,)
self.add_actions(directory = self.action_directory_changed)
self.add_callbacks(commit_list = self.item_changed)
self.connect(view.commit_list,
'itemDoubleClicked(QListWidgetItem*)',
self.item_double_clicked)
# Start at the root of the tree
model.set_directory('')
######################################################################
# Actions
def action_directory_changed(self):
"""This is called in response to a change in the the
model's directory."""
self.model.init_browser_data()
self.__display_items()
######################################################################
# Qt callbacks
def item_changed(self,*rest):
"""This is called when the current item changes in the
file/directory list(aka the commit_list)."""
current = self.view.commit_list.currentRow()
item = self.view.commit_list.item(current)
if item is None or not item.isSelected():
self.view.revision.setText('')
self.view.commit_text.setText('')
return
directories = self.model.get_directories()
directory_entries = self.model.get_directory_entries()
if current < len(directories):
# This is a directory...
dirent = directories[current]
if dirent != '..':
# This is a real directory for which
# we have child entries
entries = directory_entries[dirent]
else:
# This is '..' which is a special case
# since it doesn't really exist
entries = []
self.view.commit_text.setText('\n'.join(entries))
self.view.revision.setText('')
else:
# This is a file entry. The current row is absolute,
# so get a relative index by subtracting the number
# of directory entries
idx = current - len(directories)
if idx >= len(self.model.get_subtree_sha1s()):
# This can happen when changing directories
return
objtype, sha1, name = self.model.get_subtree_node(idx)
catguts = self.model.cat_file(objtype, sha1,
with_raw_output=True)
self.view.commit_text.setText(catguts)
self.view.revision.setText(sha1)
self.view.revision.selectAll()
# Copy the sha1 into the clipboard
qtutils.set_clipboard(sha1)
def item_double_clicked(self,*rest):
"""This is called when an entry is double-clicked.
This callback changes the model's directory when
invoked on a directory item. When invoked on a file
it allows the file to be saved."""
current = self.view.commit_list.currentRow()
directories = self.model.get_directories()
# A file item was double-clicked.
# Create a save-as dialog and export the file.
if current >= len(directories):
idx = current - len(directories)
objtype, sha1, name = self.model.get_subtree_node(idx)
nameguess = os.path.join(defaults.DIRECTORY, name)
filename = qtutils.save_dialog(self.view, 'Save', nameguess)
if not filename:
return
defaults.DIRECTORY = os.path.dirname(filename)
contents = self.model.cat_file(objtype, sha1, raw=True)
utils.write(filename, contents)
return
dirent = directories[current]
curdir = self.model.get_directory()
# '..' is a special case--it doesn't really exist...
if dirent == '..':
newdir = os.path.dirname(os.path.dirname(curdir))
if newdir == '':
self.model.set_directory(newdir)
else:
self.model.set_directory(newdir + os.sep)
else:
self.model.set_directory(curdir + dirent)
######################################################################
def __display_items(self):
"""This method populates the commit_list(aka item list)
with the current directories and items. Directories are
always listed first."""
self.view.commit_text.setText('')
self.view.revision.setText('')
dir_icon = utils.get_icon('dir.png')
file_icon = utils.get_icon('generic.png')
creator = qtutils.create_listwidget_item
qtutils.set_items(self.view.commit_list,
map(lambda d: creator(d, dir_icon),
self.model.get_directories()))
qtutils.add_items(self.view.commit_list,
map(lambda s: creator(s, file_icon),
self.model.get_subtree_names()))
+252
View File
@@ -0,0 +1,252 @@
#!/usr/bin/env python
import os
import re
import time
from PyQt4 import QtGui
from cola.observer import Observer
from cola.qobserver import QObserver
from cola.views import SearchView
from cola import qtutils
class SearchEngine(object):
def __init__(self, model):
self.model = model
self.parse = self.model.parse_rev_list
self.init()
def init(self):
pass
def get_rev_args(self):
max = self.model.get_max_results()
return { "max-count": max, "pretty": "oneline" }
def get_common_args(self):
return (self.model.get_input(), self.get_rev_args())
def search(self):
if not self.validate():
return
return self.get_results()
def validate(self):
return len(self.model.get_input()) > 1
def get_results(self):
pass
class RevisionSearch(SearchEngine):
def get_results(self):
input, args = self.get_common_args()
expr = re.compile(input)
revs = self.parse(self.model.rev_list(all=True, **args))
return [ r for r in revs if expr.match(r[0]) ]
class RevisionRangeSearch(SearchEngine):
def init(self):
self.RE = re.compile(r'[^.]*\.\..*')
def validate(self):
return bool(self.RE.match(self.model.get_input()))
def get_results(self):
input, args = self.get_common_args()
return self.parse(self.model.rev_list(input, **args))
class PathSearch(SearchEngine):
def get_results(self):
input, args = self.get_common_args()
paths = ['--'] + input.split(':')
return self.parse(self.model.rev_list(all=True, *paths, **args))
class MessageSearch(SearchEngine):
def get_results(self):
input, args = self.get_common_args()
return self.parse(
self.model.rev_list(grep=input, all=True, **args))
class AuthorSearch(SearchEngine):
def get_results(self):
input, args = self.get_common_args()
return self.parse(
self.model.rev_list(author=input, all=True, **args))
class CommitterSearch(SearchEngine):
def get_results(self):
input, args = self.get_common_args()
return self.parse(
self.model.rev_list(committer=input, all=True, **args))
class DiffSearch(SearchEngine):
def get_results(self):
input, args = self.get_common_args()
return self.parse(
self.model.log('-S'+input, all=True, **args))
class DateRangeSearch(SearchEngine):
def validate(self):
return True
def get_results(self):
args = self.get_rev_args()
start_date = self.model.get_start_date()
end_date = self.model.get_end_date()
return self.parse(self.model.rev_list(date='iso',
after=start_date,
before=end_date,
all=True,
**args))
# Modes for this controller.
# Note: names correspond to radio button names for convenience
REVISION_ID = 'radio_revision'
REVISION_RANGE = 'radio_range'
PATH = 'radio_path'
MESSAGE = 'radio_message'
DIFF = 'radio_diff'
AUTHOR = 'radio_author'
COMMITTER = 'radio_committer'
DATE_RANGE = 'radio_daterange'
# Each search type is handled by a distinct SearchEngine subclass
SEARCH_ENGINES = {
REVISION_ID: RevisionSearch,
REVISION_RANGE: RevisionRangeSearch,
PATH: PathSearch,
MESSAGE: MessageSearch,
DIFF: DiffSearch,
AUTHOR: AuthorSearch,
COMMITTER: CommitterSearch,
DATE_RANGE: DateRangeSearch,
}
class SearchController(QObserver):
def init(self, model, view):
self.add_observables('input',
'max_results',
'start_date',
'end_date')
self.add_actions(max_results = self.search_callback,
start_date = self.search_callback,
end_date = self.search_callback)
self.add_callbacks(
# Standard buttons
button_search = self.search_callback,
button_browse = self.browse_callback,
commit_list = self.display_callback,
button_export = self.export_patch,
button_cherrypick = self.cherry_pick,
# Radio buttons trigger a search
radio_revision = self.search_callback,
radio_range = self.search_callback,
radio_message = self.search_callback,
radio_path = self.search_callback,
radio_diff = self.search_callback,
radio_author = self.search_callback,
radio_committer = self.search_callback,
radio_daterange = self.search_callback,
)
self.update_fonts()
def update_fonts(self):
font = self.model.get_global_cola_fontui()
if font:
qfont = QtGui.QFont()
qfont.fromString(font)
self.view.commit_list.setFont(qfont)
font = self.model.get_global_cola_fontdiff()
if font:
qfont = QtGui.QFont()
qfont.fromString(font)
self.view.commit_text.setFont(qfont)
def set_mode(self, mode):
radio = getattr(self.view, mode)
radio.setChecked(True)
def radio_to_mode(self, radio_button):
return str(radio_button.objectName())
def get_mode(self):
for name in SEARCH_ENGINES:
radiobutton = getattr(self.view, name)
if radiobutton.isChecked():
return name
def search_callback(self, *args):
engineclass = SEARCH_ENGINES.get(self.get_mode())
if not engineclass:
print ("mode: '%s' is currently unimplemented"
% self.get_mode())
return
self.results = engineclass(self.model).search()
if self.results:
self.display_results()
else:
self.view.commit_list.clear()
self.view.commit_text.setText('')
def browse_callback(self):
paths = QtGui.QFileDialog.getOpenFileNames(self.view,
self.tr("Choose Path(s)"))
if not paths:
return
filepaths = []
lenprefix = len(os.getcwd()) + 1
for path in map(lambda x: unicode(x), paths):
if not path.startswith(os.getcwd()):
continue
filepaths.append(path[lenprefix:])
input = ':'.join(filepaths)
self.model.set_input('')
self.set_mode(PATH)
self.model.set_input(input)
def display_results(self):
commit_list = map(lambda x: x[1], self.results)
self.model.set_commit_list(commit_list)
qtutils.set_listwidget_strings(self.view.commit_list, commit_list)
def display_callback(self, *args):
widget = self.view.commit_list
row, selected = qtutils.get_selected_row(widget)
if not selected or len(self.results) < row:
return
revision = self.results[row][0]
qtutils.set_clipboard(revision)
diff = self.model.get_commit_diff(revision)
self.view.commit_text.setText(diff)
def export_patch(self):
widget = self.view.commit_list
row, selected = qtutils.get_selected_row(widget)
if not selected or len(self.results) < row:
return
revision = self.results[row][0]
qtutils.log(self.model.export_patchset(revision, revision),
doraise=True,
quiet=False)
def cherry_pick(self):
widget = self.view.commit_list
row, selected = qtutils.get_selected_row(widget)
if not selected or len(self.results) < row:
return
revision = self.results[row][0]
qtutils.log(self.model.cherry_pick(revision),
doraise=True,
quiet=False)
def search_commits(model, mode, browse):
def get_date(timespec):
return '%04d-%02d-%02d' % time.localtime(timespec)[:3]
model = model.clone()
model.create(input='',
max_results=500,
start_date='',
end_date='',
commit_list=None)
view = SearchView(None)
ctl = SearchController(model, view)
ctl.set_mode(mode)
model.set_start_date(get_date(time.time()-(87640*7)))
model.set_end_date(get_date(time.time()+87640))
view.show()
if browse:
ctl.browse_callback()
view.exec_()
+111
View File
@@ -0,0 +1,111 @@
import os
from cola import utils
from cola import qtutils
from cola.qobserver import QObserver
from cola.views import StashView
def stash(model, parent):
model = model.clone()
model.create( stash_list=[], stash_revids=[] )
view = StashView(parent)
ctl = StashController(model, view)
view.show()
class StashController(QObserver):
def init (self, model, view):
self.add_observables('stash_list')
self.add_callbacks(button_stash_show = self.stash_show,
button_stash_apply = self.stash_apply,
button_stash_drop = self.stash_drop,
button_stash_clear = self.stash_clear,
button_stash_save = self.stash_save)
self.update_model()
def update_model(self):
self.model.set_stash_list(self.model.parse_stash_list())
self.model.set_stash_revids(self.model.parse_stash_list(revids=True))
self.refresh_view()
def get_selected_stash(self):
list_widget = self.view.stash_list
stash_list = self.model.get_stash_revids()
return qtutils.get_selected_item(list_widget, stash_list)
def stash_save(self):
if not qtutils.question(self.view,
self.tr('Stash Changes?'),
self.tr('This will stash your current '
'changes away for later use.\n'
'Continue?')):
return
stash_name, ok = qtutils.input(self.tr('Enter a name for this stash'))
if not ok:
return
while stash_name in self.model.get_stash_list():
qtutils.information(self.tr("Oops!"),
self.tr('That name already exists. '
'Please enter another name.'))
stash_name, ok = qtutils.input(self.tr("Enter a name for this stash"))
if not ok:
return
if not stash_name:
return
# Sanitize our input, just in case
stash_name = utils.sanitize_input(stash_name)
qtutils.log(self.model.stash('save', stash_name),
quiet=False,
doraise=True)
self.view.accept()
def stash_show(self):
"""Shows the current stash in the main view."""
selection = self.get_selected_stash()
if not selection:
return
diffstat = self.model.stash('show', selection)
diff = self.model.stash('show', '-p', selection)
self.view.parent_view.display('%s\n\n%s' % (diffstat, diff))
def stash_apply(self):
selection = self.get_selected_stash()
if not selection:
return
(status, stdout, stderr) = self.model.stash('pop', selection,
with_extended_output=True)
qtutils.log(stdout + stderr,
quiet=False,
doraise=True)
self.view.accept()
def stash_drop(self):
selection = self.get_selected_stash()
if not selection:
return
if not qtutils.question(self.view,
self.tr('Drop Stash?'),
self.tr('This will permanently remove the '
'selected stash.\n'
'Recovering these changes may not '
'be possible.\n\n'
'Continue?')):
return
qtutils.log(self.model.stash('drop', selection),
quiet=False,
doraise=True)
self.update_model()
def stash_clear(self):
if not qtutils.question(self.view,
self.tr('Drop All Stashes?'),
self.tr('This will permanently remove '
'ALL stashed changes.\n'
'Recovering these changes may not '
'be possible.\n\n'
'Continue?')):
return
self.model.stash('clear'),
self.update_model()
+296
View File
@@ -0,0 +1,296 @@
#!/usr/bin/env python
import os
import time
from PyQt4.QtGui import QDialog
from PyQt4.QtGui import QFont
from cola import utils
from cola import qtutils
from cola.model import Model
from cola.views import ListView
from cola.views import ComboView
from cola.views import CommitView
from cola.views import OptionsView
from cola.views import LogView
from cola.qobserver import QObserver
def set_diff_font(model, widget):
if model.has_param('global_cola_fontdiff'):
font = model.get_param('global_cola_fontdiff')
if not font:
return
qf = QFont()
qf.fromString(font)
widget.setFont(qf)
def choose_from_combo(title, parent, branches):
return ComboView(parent, title, branches).get_selected()
def choose_from_list(title, parent, items=[]):
return ListView(parent, title, items).get_selected()
#+-------------------------------------------------------------
def select_commits(model, parent, title, revs, summaries):
"""Use the CommitView to select commits from a list."""
model = model.clone()
model.set_revisions(revs)
model.set_summaries(summaries)
view = CommitView(parent, title)
ctl = SelectCommitsController(model, view)
return ctl.select_commits()
class SelectCommitsController(QObserver):
def init(self, model, view):
set_diff_font(model, view.commit_text)
self.connect(view.commit_list,
'itemSelectionChanged()',
self.commit_sha1_selected)
def select_commits(self):
summaries = self.model.get_summaries()
if not summaries:
msg = self.tr('No commits exist in this branch.')
qtutils.show_output(msg)
return []
qtutils.set_items(self.view.commit_list, summaries)
self.view.show()
if self.view.exec_() != QDialog.Accepted:
return []
revs = self.model.get_revisions()
list_widget = self.view.commit_list
return qtutils.get_selection_list(list_widget, revs)
def commit_sha1_selected(self):
row, selected = qtutils.get_selected_row(self.view.commit_list)
if not selected:
self.view.commit_text.setText('')
self.view.revision.setText('')
return
# Get the sha1 and put it in the revision line
sha1 = self.model.get_revision_sha1(row)
self.view.revision.setText(sha1)
self.view.revision.selectAll()
# Lookup the sha1's commit
commit_diff = self.model.get_commit_diff(sha1)
self.view.commit_text.setText(commit_diff)
# Copy the sha1 into the clipboard
qtutils.set_clipboard(sha1)
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#+ The Options GUI Controller
def update_options(model, parent):
view = OptionsView(parent)
ctl = OptionsController(model,view)
view.show()
return view.exec_() == QDialog.Accepted
class OptionsController(QObserver):
"""ASSUMPTIONS:
This controller assumes that the view's widgets are named
the same as the model parameters."""
def init(self,model,view):
# used for telling about interactive font changes
self.original_model = model
model = model.clone()
self.add_observables('local_user_email',
'local_user_name',
'local_merge_summary',
'local_merge_diffstat',
'local_merge_verbosity',
'local_gui_diffcontext',
'global_user_email',
'global_user_name',
'global_merge_summary',
'global_merge_diffstat',
'global_merge_verbosity',
'global_gui_editor',
'global_gui_diffeditor',
'global_gui_diffcontext',
'global_gui_historybrowser',
'global_cola_fontdiff_size',
'global_cola_fontdiff',
'global_cola_fontui_size',
'global_cola_fontui',
'global_cola_savewindowsettings',
'global_cola_saveatexit')
self.add_actions(global_cola_fontdiff_size = self.update_size)
self.add_actions(global_cola_fontui_size = self.update_size)
self.add_actions(global_cola_fontdiff = self.tell_parent_model)
self.add_actions(global_cola_fontui = self.tell_parent_model)
self.add_callbacks(save_button = self.save_settings)
self.connect(self.view, 'rejected()', self.restore_settings)
self.refresh_view()
self.backup_model = self.model.clone()
def refresh_view(self):
font = self.model.get_param('global_cola_fontui')
if font:
size = int(font.split(',')[1])
self.view.global_cola_fontui_size.setValue(size)
self.model.set_global_cola_fontui_size(size)
fontui = QFont()
fontui.fromString(font)
self.view.global_cola_fontui.setCurrentFont(fontui)
font = self.model.get_global_cola_fontdiff()
if font:
size = int(font.split(',')[1])
self.view.global_cola_fontdiff_size.setValue(size)
self.model.set_global_cola_fontdiff_size(size)
fontdiff = QFont()
fontdiff.fromString(font)
self.view.global_cola_fontdiff.setCurrentFont(fontdiff)
self.view.local_groupbox.setTitle(unicode(self.tr('%s Repository'))
% self.model.get_project())
QObserver.refresh_view(self)
# save button
def save_settings(self):
params_to_save = []
params = self.model.get_config_params()
for param in params:
value = self.model.get_param(param)
backup = self.backup_model.get_param(param)
if value != backup:
params_to_save.append(param)
for param in params_to_save:
self.model.save_config_param(param)
self.original_model.copy_params(self.model, params_to_save)
self.view.done(QDialog.Accepted)
# cancel button -> undo changes
def restore_settings(self):
params = self.backup_model.get_config_params()
self.model.copy_params(self.backup_model, params)
self.tell_parent_model()
def tell_parent_model(self,*rest):
params= ('global_cola_fontdiff',
'global_cola_fontui',
'global_cola_fontdiff_size',
'global_cola_fontui_size',
'global_cola_savewindowsettings',
'global_cola_saveatexit')
for param in params:
self.original_model.set_param(
param, self.model.get_param(param))
def update_size(self, *rest):
combo = self.view.global_cola_fontui
param = str(combo.objectName())
default = str(combo.currentFont().toString())
self.model.apply_font_size(param, default)
combo = self.view.global_cola_fontdiff
param = str(combo.objectName())
default = str(combo.currentFont().toString())
self.model.apply_font_size(param, default)
self.tell_parent_model()
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#+ The Log GUI Controller
def logger():
model = Model(search_text = '')
view = LogView(None)
ctl = LogController(model,view)
return view
class LogController(QObserver):
def init(self, model, view):
self.add_observables('search_text')
self.add_actions(search_text = self.insta_search)
self.add_callbacks(clear_button = self.clear,
next_button = self.next,
prev_button = self.prev)
self.connect(self.view.output_text,
'cursorPositionChanged()',
self.cursor_position_changed)
self.search_offset = 0
def insta_search(self,*rest):
self.search_offset = 0
txt = self.model.get_search_text().lower()
if len(txt.strip()):
self.next()
else:
cursor = self.view.output_text.textCursor()
cursor.clearSelection()
self.view.output_text.setTextCursor(cursor)
def clear(self):
self.view.output_text.clear()
self.search_offset = 0
def next(self):
text = self.model.get_search_text().lower().strip()
if not text:
return
output = str(self.view.output_text.toPlainText())
if self.search_offset + len(text) > len(output):
title = unicode(self.tr('%s not found')) % text
question = unicode(self.tr("Could not find '%s'.\n"
'Search from the beginning?')) % text
if qtutils.question(self.view, title, question, default=False):
self.search_offset = 0
else:
return
find_in = output[self.search_offset:].lower()
try:
index = find_in.index(text)
except:
self.search_offset = 0
title = unicode(self.tr("%s not found")) % text
question = unicode(self.tr("Could not find '%s'.\n"
'Search from the beginning?')) % text
if qtutils.question(self.view, title, default=False):
self.next()
return
cursor = self.view.output_text.textCursor()
offset = self.search_offset + index
new_offset = offset + len(text)
cursor.setPosition(offset)
cursor.setPosition(new_offset, cursor.KeepAnchor)
self.view.output_text.setTextCursor(cursor)
self.search_offset = new_offset
def prev(self):
text = self.model.get_search_text().lower().strip()
if not text:
return
output = str(self.view.output_text.toPlainText())
if self.search_offset == 0:
self.search_offset = len(output)
find_in = output[:self.search_offset].lower()
try:
offset = find_in.rindex(text)
except:
self.search_offset = 0
title = unicode(self.tr('%s not found')) % text
question = unicode(self.tr("Could not find '%s'.\n"
'Search from the end?')) % text
if qtutils.question(self.view, title, question):
self.prev()
return
cursor = self.view.output_text.textCursor()
new_offset = offset + len(text)
cursor.setPosition(offset)
cursor.setPosition(new_offset, cursor.KeepAnchor)
self.view.output_text.setTextCursor(cursor)
self.search_offset = offset
def cursor_position_changed(self):
cursor = self.view.output_text.textCursor()
self.search_offset = cursor.selectionStart()
+7
View File
@@ -0,0 +1,7 @@
#!/usr/bin/env python
import Common
def build(bld):
# Compile the cola support modules
pyqt = bld.create_obj('py')
pyqt.inst_var = 'COLA_CONTROLLERS'
pyqt.find_sources_in_dirs('.')
+15
View File
@@ -0,0 +1,15 @@
#!/usr/bin/env python
import os
from PyQt4.QtCore import QEvent
from cola import version
VERSION = version.VERSION
DIRECTORY = os.getcwd()
WIDTH = 780
HEIGHT = 600
X = 262
Y = 254
INOTIFY_EVENT = QEvent.User + 0
Executable
+85
View File
@@ -0,0 +1,85 @@
#!/usr/bin/env python
# Copyright(C) 2007, David Aguilar <davvid@gmail.com>
import os
import sys
import optparse
from os.path import join
from os.path import dirname
from os.path import realpath
def setup_environment():
"""Prepends sys.path with cola's module path."""
# ex: sys.path = [ /usr/share, ... ]
# from cola import utils
# implies: utils = /usr/share/cola/utils.py
cola = realpath(sys.argv[0])
bin = dirname(cola)
prefix = dirname(bin)
share = join(prefix,'share')
sys.path.insert(0, share)
def main():
parser = optparse.OptionParser(
usage='%prog /repo/path-1 ... /repo/path-N*\n\n'
'*the current directory is used when no '
'paths are specified.')
parser.add_option('-v', '--version',
help='Show cola version',
dest='version',
default=False,
action='store_true')
opts, args = parser.parse_args()
# allow "git cola /path/to/repo"
if args:
os.chdir(os.path.realpath(args[0]))
# This sets up sys.path so that the cola modules can be found.
setup_environment()
if opts.version:
from cola.version import VERSION
print "cola version", VERSION
sys.exit(0)
# allow "git cola /repo/path-1 .. /repo/path-N
from cola.models import Model
from cola import utils
from cola.views import View
from cola.controllers import Controller
if args[1:]:
for repo in [ os.path.realpath(r) for r in args[1:] ]:
utils.fork("git", "cola", repo)
# load the model right away so that we can bail out when
# no git repo exists
model = Model()
# PyQt4
try:
from PyQt4 import QtCore
from PyQt4 import QtGui
except ImportError:
print "Sorry, you do not seem to have PyQt4 installed."
print "Please install it before using cola."
print "e.g.: sudo apt-get install python-qt4"
sys.exit(-1)
app = QtGui.QApplication(sys.argv)
app.setWindowIcon(QtGui.QIcon( utils.get_icon('git.png')) )
locale = str(QtCore.QLocale().system().name())
qmfile = utils.get_qm_for_locale( locale )
if os.path.exists(qmfile):
translator = QtCore.QTranslator()
translator.load(qmfile)
app.installTranslator(translator)
# simple mvc
view = View(app.activeWindow())
ctl = Controller(model, view)
view.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
+192
View File
@@ -0,0 +1,192 @@
"""
This module is from http://gitorious.org/projects/git-python/
and is included here for convenience.
"""
import os
import subprocess
def dashify(string):
return string.replace('_', '-')
class MethodMissingMixin(object):
"""
A Mixin' to implement the 'method_missing' Ruby-like protocol.
This was `taken from a blog post <http://blog.iffy.us/?p=43>`_
"""
def __getattr__(self, attr):
class MethodMissing(object):
def __init__(self, wrapped, method):
self.__wrapped__ = wrapped
self.__method__ = method
def __call__(self, *args, **kwargs):
return self.__wrapped__.method_missing(self.__method__, *args, **kwargs)
return MethodMissing(self, attr)
def method_missing(self, *args, **kwargs):
""" This method should be overridden in the derived class. """
raise NotImplementedError(str(self.__wrapped__) + " 'method_missing' method has not been implemented.")
# Enables debugging of GitPython's git commands
GIT_PYTHON_TRACE = os.environ.get("GIT_PYTHON_TRACE", False)
execute_kwargs = ('istream', 'with_keep_cwd', 'with_extended_output',
'with_exceptions', 'with_raw_output')
class Git(MethodMissingMixin):
"""
The Git class manages communication with the Git binary
"""
def __init__(self, git_dir):
super(Git, self).__init__()
self.git_dir = git_dir
@property
def get_dir(self):
return self.git_dir
def execute(self, command,
istream=None,
with_keep_cwd=False,
with_extended_output=False,
with_exceptions=True,
with_raw_output=False,
):
"""
Handles executing the command on the shell and consumes and returns
the returned information (stdout)
``command``
The command argument list to execute
``istream``
Standard input filehandle passed to subprocess.Popen.
``with_keep_cwd``
Whether to use the current working directory from os.getcwd().
GitPython uses get_work_tree() as its working directory by
default and get_git_dir() for bare repositories.
``with_extended_output``
Whether to return a (status, stdout, stderr) tuple.
``with_exceptions``
Whether to raise an exception when git returns a non-zero status.
``with_raw_output``
Whether to avoid stripping off trailing whitespace.
Returns
str(output) # extended_output = False (Default)
tuple(int(status), str(output)) # extended_output = True
"""
if GIT_PYTHON_TRACE and not GIT_PYTHON_TRACE == 'full':
print ' '.join(command)
# Allow the user to have the command executed in their working dir.
if with_keep_cwd or self.git_dir is None:
cwd = os.getcwd()
else:
cwd=self.git_dir
# Start the process
proc = subprocess.Popen(command,
cwd=cwd,
stdin=istream,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE
)
# Wait for the process to return
try:
stdout_value = proc.stdout.read()
stderr_value = proc.stderr.read()
status = proc.wait()
finally:
proc.stdout.close()
proc.stderr.close()
# Strip off trailing whitespace by default
if not with_raw_output:
stdout_value = stdout_value.rstrip()
stderr_value = stderr_value.rstrip()
if with_exceptions and status != 0:
raise GitCommandError(command, status, stderr_value)
if GIT_PYTHON_TRACE == 'full':
if stderr_value:
print "%s -> %d: '%s' !! '%s'" % (command, status, stdout_value, stderr_value)
elif stdout_value:
print "%s -> %d: '%s'" % (command, status, stdout_value)
else:
print "%s -> %d" % (command, status)
# Allow access to the command's status code
if with_extended_output:
return (status, stdout_value, stderr_value)
else:
return stdout_value
def transform_kwargs(self, **kwargs):
"""
Transforms Python style kwargs into git command line options.
"""
args = []
for k, v in kwargs.items():
if len(k) == 1:
if v is True:
args.append("-%s" % k)
elif type(v) is not bool:
args.append("-%s%s" % (k, v))
else:
if v is True:
args.append("--%s" % dashify(k))
elif type(v) is not bool:
args.append("--%s=%s" % (dashify(k), v))
return args
def method_missing(self, method, *args, **kwargs):
"""
Run the given git command with the specified arguments and return
the result as a String
``method``
is the command
``args``
is the list of arguments
``kwargs``
is a dict of keyword arguments.
This function accepts the same optional keyword arguments
as execute().
Examples
git.rev_list('master', max_count=10, header=True)
Returns
Same as execute()
"""
# Handle optional arguments prior to calling transform_kwargs
# otherwise these'll end up in args, which is bad.
_kwargs = {}
for kwarg in execute_kwargs:
try:
_kwargs[kwarg] = kwargs.pop(kwarg)
except KeyError:
pass
# Prepare the argument list
opt_args = self.transform_kwargs(**kwargs)
ext_args = map(str, args)
args = opt_args + ext_args
call = ["git", dashify(method)]
call.extend(args)
return self.execute(call, **_kwargs)
+68
View File
@@ -0,0 +1,68 @@
#!/usr/bin/env python
import os
import time
from PyQt4.QtCore import QCoreApplication
from PyQt4.QtCore import QThread
from PyQt4.QtCore import QEvent
from PyQt4.QtCore import SIGNAL
from pyinotify import ProcessEvent
from pyinotify import WatchManager, Notifier, EventsCodes
from models import GitCola
import defaults
class FileSysEvent(ProcessEvent):
def __init__(self, parent):
ProcessEvent.__init__(self)
self.parent = parent
self.last_event_time = time.time()
def process_default(self, event):
# Prevent notificaiton floods
if time.time() - self.last_event_time > 1.0:
self.parent.notify()
self.last_event_time = time.time()
class GitNotifier(QThread):
def __init__(self, receiver, path):
QThread.__init__(self)
self.git = GitCola()
self.receiver = receiver
self.path = path
self.abort = False
self.dirs_seen = {}
self.mask = (EventsCodes.IN_CREATE |
EventsCodes.IN_DELETE |
EventsCodes.IN_MODIFY |
EventsCodes.IN_MOVED_TO)
def notify(self):
if not self.abort:
event_type = QEvent.Type(defaults.INOTIFY_EVENT)
event = QEvent(event_type)
QCoreApplication.postEvent(self.receiver, event)
def watch_directory(self, directory):
directory = os.path.realpath(directory)
if directory not in self.dirs_seen:
self.wm.add_watch(directory, self.mask)
self.dirs_seen[directory] = True
def run(self):
# Only capture those events that git cares about
self.wm = WatchManager()
notifier = Notifier(self.wm, FileSysEvent(self))
self.notifier = notifier
dirs_seen = {}
added_flag = False
while not self.abort:
if not added_flag:
self.watch_directory(self.path)
# Register files/directories known to git
for file in self.git.ls_files().splitlines():
directory = os.path.dirname(file)
self.watch_directory(directory)
added_flag = True
notifier.process_events()
if notifier.check_events(timeout=250):
notifier.read_events()
notifier.stop()
+368
View File
@@ -0,0 +1,368 @@
#!/usr/bin/env python
import os
import imp
from cStringIO import StringIO
from types import DictType
from types import ListType
from types import TupleType
from types import StringTypes
from types import BooleanType
from types import IntType
from types import LongType
from types import FloatType
from types import ComplexType
from types import InstanceType
class Observable(object):
"""Handles subject/observer notifications."""
def __init__(self,*args,**kwargs):
self.__observers = []
self.__notify = True
def get_notify(self):
return self.__notify
def set_notify(self, notify=True):
self.__notify = notify
def add_observer(self, observer):
if observer not in self.__observers:
self.__observers.append(observer)
def remove_observer(self, observer):
if observer in self.__observers:
self.__observers.remove(observer)
def notify_observers(self, *param):
if not self.__notify: return
for observer in self.__observers:
observer.notify(*param)
class Model(Observable):
"""Creates a generic model object with params specified
as a name:value dictionary.
get_name() and set_name(value) are created automatically
for any of the parameters specified in the **kwargs"""
def __init__(self, *args, **kwargs):
Observable.__init__(self)
self.__params = []
# For meta-programmability
self.from_dict(kwargs)
self.init()
# for subclasses
def init(self):
pass
def create(self,**kwargs):
return self.from_dict(kwargs)
def get_param_names(self):
return tuple(self.__params)
def notify_all(self):
self.notify_observers(*self.get_param_names())
def clone(self, *args, **kwargs):
return self.__class__(*args, **kwargs).from_dict(self.to_dict())
def has_param(self,param):
return param in self.__params
def get_param(self,param):
return getattr(self, param)
def __getattr__(self, param):
"""Provides automatic get/set/add/append methods."""
# Base case: we actually have this param
if param in self.__dict__:
return getattr(self, param)
# Check for the translated variant of the param
realparam = self.__translate(param, sep='')
if realparam in self.__dict__:
return getattr(self, realparam)
if realparam.startswith("get"):
param = self.__translate(param, "get")
return lambda: getattr(self, param)
elif realparam.startswith("set"):
param = self.__translate(param, "set")
return lambda v: self.set_param(param, v,
check_params=True)
elif (realparam.startswith("add") or realparam.startswith("append")):
if realparam.startswith("add"):
param = self.__translate(realparam, "add")
else:
param = self.__translate(realparam, "append")
def array_append(*values):
array = getattr(self, param)
if array is None:
classnm = self.__class__.__name__
errmsg = ("%s object has no array named '%s'"
%( classnm, param ))
raise AttributeError(errmsg)
else:
array.extend(values)
# Cache the function definition
setattr(self, realparam, array_append)
return array_append
errmsg = ("%s object has no parameter '%s'"
% (self.__class__.__name__, param))
raise AttributeError(errmsg)
def set_param(self, param, value, notify=True, check_params=False):
"""Set param with optional notification and validity checks."""
param = param.lower()
if check_params and param not in self.__params:
raise Exception("Parameter '%s' not available for %s"
% (param, self.__class__.__name__))
elif param not in self.__params:
self.__params.append(param)
setattr(self, param, value)
if notify:
self.notify_observers(param)
def copy_params(self, model, params=None):
if params is None:
params = self.get_param_names()
for param in params:
self.set_param(param, model.get_param(param))
def __translate(self, param, prefix='', sep='_'):
"""Translates an param name from the external name
used in methods to those used internally. The default
settings strip off '_' so that both get_foo() and getFoo()
are valid incantations."""
return param[len(prefix):].lstrip(sep).lower()
def save(self, filename):
if not has_json():
return
import simplejson
file = open(filename, 'w')
simplejson.dump(self.to_dict(), file, indent=4)
file.close()
def load(self, filename):
if not has_json():
return
import simplejson
file = open(filename, 'r')
ddict = simplejson.load(file)
file.close()
if "__class__" in ddict:
# load params in-place.
del ddict["__class__"]
return self.from_dict(ddict)
@staticmethod
def instance(filename):
if not has_json():
return
import simplejson
file = open(filename, 'r')
ddict = simplejson.load(file)
file.close()
if "__class__" in ddict:
cls = Model.str_to_class(ddict["__class__"])
del ddict["__class__"]
return cls().from_dict(ddict)
else:
return Model().from_dict(ddict)
def from_dict(self, source_dict):
"""Import a complex model from a dictionary.
We store class information in the __class__ variable.
If it looks like a duck, it's a duck."""
if "__class__" in source_dict:
clsstr = source_dict["__class__"]
del source_dict["__class__"]
cls = Model.str_to_class(clsstr)
return cls().from_dict(source_dict)
# Not initiating a clone: load parameters in-place
for param, val in source_dict.iteritems():
self.set_param(param,
self.__obj_from_value(val),
notify=False)
self.__params.sort()
return self
def __obj_from_value(self, val):
# Atoms
if is_atom(val):
return val
# Possibly nested lists
elif is_list(val):
return [ self.__obj_from_value(v) for v in val ]
elif is_dict(val):
# A param that maps to a Model-object
if "__class__" in val:
clsstr = val["__class__"]
cls = Model.str_to_class(clsstr)
del val["__class__"]
return cls().from_dict(val)
newdict = {}
for k, v in val.iteritems():
newdict[k] = self.__obj_from_value(v)
return newdict
# All others
return val
def to_dict(self):
"""
Exports a model to a dictionary.
This simplifies serialization.
"""
params = {"__class__": Model.class_to_str(self)}
for param in self.__params:
params[param] = self.__obj_to_value(getattr(self, param))
return params
def __obj_to_value(self, item):
if is_atom(item):
return item
elif is_list(item):
newlist = [ self.__obj_to_value(i) for i in item ]
return newlist
elif is_dict(item):
newdict = {}
for k,v in item.iteritems():
newdict[k] = self.__obj_to_value(v)
return newdict
elif is_instance(item):
return item.to_dict()
else:
raise NotImplementedError("Unknown type:" + str(type(item)))
__INDENT__ = 0
__PREINDENT__ = True
__STRSTACK__ = []
@staticmethod
def INDENT(i=0):
Model.__INDENT__ += i
return '\t' * Model.__INDENT__
def __str__(self):
"""A convenient, recursively-defined stringification method."""
# This avoid infinite recursion on cyclical structures
if self in Model.__STRSTACK__:
return 'REFERENCE' # TODO: implement references?
else:
Model.__STRSTACK__.append(self)
io = StringIO()
if Model.__PREINDENT__:
io.write(Model.INDENT())
io.write(self.__class__.__name__ + '(')
Model.INDENT(1)
for param in self.__params:
if param.startswith('_'):
continue
io.write('\n')
inner = Model.INDENT() + param + " = "
value = getattr(self, param)
if type(value) == ListType:
indent = Model.INDENT(1)
io.write(inner + "[\n")
for val in value:
if is_model(val):
io.write(str(val)+'\n')
else:
io.write(indent)
io.write(str(val))
io.write(",\n")
io.write(Model.INDENT(-1))
io.write("],")
else:
Model.__PREINDENT__ = False
io.write(inner)
io.write(str(value))
io.write(',')
Model.__PREINDENT__ = True
io.write('\n' + Model.INDENT(-1) + ')')
value = io.getvalue()
io.close()
Model.__STRSTACK__.remove(self)
return value
@staticmethod
def str_to_class(clstr):
items = clstr.split('.')
modules = items[:-1]
classname = items[-1]
path = None
module = None
for mod in modules:
search = imp.find_module(mod, path)
try:
module = imp.load_module(mod, *search)
if hasattr(module, "__path__"):
path = module.__path__
finally:
if search and search[0]:
search[0].close()
if module:
return getattr(module, classname)
else:
raise Exception("No class found for: %s" % clstr)
@staticmethod
def class_to_str(instance):
modname = instance.__module__
classname = instance.__class__.__name__
return "%s.%s" % (modname, classname)
#############################################################################
def has_json():
try:
import simplejson
return True
except ImportError:
print "Unable to import simplejson." % action
print "You do not have simplejson installed."
print "try: sudo apt-get install simplejson"
return False
#############################################################################
def is_model(item):
return issubclass(item.__class__, Model)
def is_dict(item):
return type(item) is DictType
def is_list(item):
return type(item) is ListType or type(item) is TupleType
def is_atom(item):
return(type(item) in StringTypes
or type(item) is BooleanType
or type(item) is IntType
or type(item) is LongType
or type(item) is FloatType
or type(item) is ComplexType)
def is_instance(item):
return(is_model(item) or type(item) is InstanceType)
+958
View File
@@ -0,0 +1,958 @@
import os
import sys
import re
import time
import subprocess
from cStringIO import StringIO
from cola import git
from cola import utils
from cola import model
#+-------------------------------------------------------------------------
#+ A regex for matching the output of git(log|rev-list) --pretty=oneline
REV_LIST_REGEX = re.compile('([0-9a-f]+)\W(.*)')
#+-------------------------------------------------------------------------
# List of functions available directly through model.command_name()
GIT_COMMANDS = """
am annotate apply archive archive_recursive
bisect blame branch bundle
checkout checkout_index cherry cherry_pick citool
clean commit config count_objects
describe diff
fast_export fetch filter_branch format_patch fsck
gc get_tar_commit_id grep gui
hard_repack imap_send init instaweb
log lost_found ls_files ls_remote ls_tree
merge mergetool mv name_rev pull push
read_tree rebase relink remote repack
request_pull reset revert rev_list rm
send_email shortlog show show_branch
show_ref stash status submodule svn
tag var verify_pack whatchanged
""".split()
class GitCola(git.Git):
"""GitPython throws exceptions by default.
We suppress exceptions in favor of return values.
"""
def __init__(self):
self._git_dir = None
self._work_tree = None
git_dir = self.get_git_dir()
work_tree = self.get_work_tree()
if work_tree:
os.chdir(work_tree)
else:
print "Sorry, git-cola requires a work tree"
sys.exit(-1)
git.Git.__init__(self, work_tree)
def execute(*args, **kwargs):
kwargs['with_exceptions'] = False
return git.Git.execute(*args, **kwargs)
def get_work_tree(self):
if not self._work_tree:
self._work_tree = os.getenv('GIT_WORK_TREE')
if not self._work_tree or not os.path.isdir(self._work_tree):
self._work_tree = os.path.abspath(
os.path.join(self._git_dir, '..'))
return self._work_tree
def get_git_dir(self):
if not self._git_dir:
self._git_dir = os.getenv('GIT_DIR')
if self._git_dir and self._is_git_dir(self._git_dir):
return self._git_dir
curpath = os.getcwd()
while curpath:
if self._is_git_dir(curpath):
self._git_dir = curpath
break
gitpath = os.path.join(curpath, '.git')
if self._is_git_dir(gitpath):
self._git_dir = gitpath
break
curpath, dummy = os.path.split(curpath)
if not dummy:
break
return self._git_dir
def _is_git_dir(self, d):
""" This is taken from the git setup.c:is_git_directory
function."""
if (os.path.isdir(d)
and os.path.isdir(os.path.join(d, 'objects'))
and os.path.isdir(os.path.join(d, 'refs'))):
headref = os.path.join(d, 'HEAD')
return (os.path.isfile(headref)
or (os.path.islink(headref)
and os.readlink(headref).startswith('refs')))
return False
class Model(model.Model):
"""Provides a friendly wrapper for doing commit git operations."""
def init(self):
"""Reads git repository settings and sets several methods
so that they refer to the git module. This object is
encapsulates cola's interaction with git."""
# chdir to the root of the git tree.
# This keeps paths relative.
self.git = GitCola()
# Read git config
self.__init_config_data()
# Import all git commands from git.py
for cmd in GIT_COMMANDS:
setattr(self, cmd, getattr(self.git, cmd))
self.create(
#####################################################
# Used in various places
currentbranch = '',
remotes = [],
remotename = '',
local_branch = '',
remote_branch = '',
search_text = '',
git_version = self.git.version(),
#####################################################
# Used primarily by the main UI
project = os.path.basename(os.getcwd()),
commitmsg = '',
modified = [],
staged = [],
unstaged = [],
untracked = [],
window_geom = utils.parse_geom(self.get_global_cola_geometry()),
#####################################################
# Used by the create branch dialog
revision = '',
local_branches = [],
remote_branches = [],
tags = [],
#####################################################
# Used by the commit/repo browser
directory = '',
revisions = [],
summaries = [],
# These are parallel lists
types = [],
sha1s = [],
names = [],
# All items below here are re-calculated in
# init_browser_data()
directories = [],
directory_entries = {},
# These are also parallel lists
subtree_types = [],
subtree_sha1s = [],
subtree_names = [],
)
def __init_config_data(self):
"""Reads git config --list and creates parameters
for each setting."""
# These parameters are saved in .gitconfig,
# so ideally these should be as short as possible.
# config items that are controllable globally
# and per-repository
self.__local_and_global_defaults = {
'user_name': '',
'user_email': '',
'merge_summary': False,
'merge_diffstat': True,
'merge_verbosity': 2,
'gui_diffcontext': 3,
'gui_pruneduringfetch': False,
}
# config items that are purely git config --global settings
self.__global_defaults = {
'cola_geometry':'',
'cola_fontui': '',
'cola_fontui_size':12,
'cola_fontdiff': '',
'cola_fontdiff_size':12,
'cola_savewindowsettings': False,
'cola_saveatexit': False,
'gui_editor': 'gvim',
'gui_diffeditor': 'xxdiff',
'gui_historybrowser': 'gitk',
}
local_dict = self.config_dict(local=True)
global_dict = self.config_dict(local=False)
for k,v in local_dict.iteritems():
self.set_param('local_'+k, v)
for k,v in global_dict.iteritems():
self.set_param('global_'+k, v)
if k not in local_dict:
local_dict[k]=v
self.set_param('local_'+k, v)
# Bootstrap the internal font*_size variables
for param in ('global_cola_fontui', 'global_cola_fontdiff'):
if hasattr(self, param):
font = self.get_param(param)
if font:
size = int(font.split(',')[1])
self.set_param(param+'_size', size)
param = param[len('global_'):]
global_dict[param] = font
global_dict[param+'_size'] = size
# Load defaults for all undefined items
local_and_global_defaults = self.__local_and_global_defaults
for k,v in local_and_global_defaults.iteritems():
if k not in local_dict:
self.set_param('local_'+k, v)
if k not in global_dict:
self.set_param('global_'+k, v)
global_defaults = self.__global_defaults
for k,v in global_defaults.iteritems():
if k not in global_dict:
self.set_param('global_'+k, v)
# Allow EDITOR/DIFF_EDITOR environment variable overrides
self.global_gui_editor = os.getenv('GUI_EDITOR',
self.global_gui_editor)
self.global_gui_diffeditor = os.getenv('DIFF_EDITOR',
self.global_gui_diffeditor)
# Load the diff context
self.diff_context = self.local_gui_diffcontext
def branch_list(self, remote=False):
branches = map(lambda x: x.lstrip('* '),
self.git.branch(r=remote).splitlines())
if remote:
remotes = []
for branch in branches:
if branch.endswith('/HEAD'):
continue
remotes.append(branch)
return remotes
return branches
def get_config_params(self):
params = []
params.extend(map(lambda x: 'local_' + x,
self.__local_and_global_defaults.keys()))
params.extend(map(lambda x: 'global_' + x,
self.__local_and_global_defaults.keys()))
params.extend(map(lambda x: 'global_' + x,
self.__global_defaults.keys()))
return params
def save_config_param(self, param):
if param not in self.get_config_params():
return
value = self.get_param(param)
if param == 'local_gui_diffcontext':
self.diff_context = value
if param.startswith('local_'):
param = param[len('local_'):]
is_local = True
elif param.startswith('global_'):
param = param[len('global_'):]
is_local = False
else:
raise Exception("Invalid param '%s' passed to " % param
+'save_config_param()')
param = param.replace('_', '.') # model -> git
return self.config_set(param, value, local=is_local)
def init_browser_data(self):
"""This scans over self.(names, sha1s, types) to generate
directories, directory_entries, and subtree_*"""
# Collect data for the model
if not self.get_currentbranch(): return
self.subtree_types = []
self.subtree_sha1s = []
self.subtree_names = []
self.directories = []
self.directory_entries = {}
# Lookup the tree info
tree_info = self.parse_ls_tree(self.get_currentbranch())
self.set_types(map( lambda(x): x[1], tree_info ))
self.set_sha1s(map( lambda(x): x[2], tree_info ))
self.set_names(map( lambda(x): x[3], tree_info ))
if self.directory: self.directories.append('..')
dir_entries = self.directory_entries
dir_regex = re.compile('([^/]+)/')
dirs_seen = {}
subdirs_seen = {}
for idx, name in enumerate(self.names):
if not name.startswith(self.directory):
continue
name = name[ len(self.directory): ]
if name.count('/'):
# This is a directory...
match = dir_regex.match(name)
if not match:
continue
dirent = match.group(1) + '/'
if dirent not in self.directory_entries:
self.directory_entries[dirent] = []
if dirent not in dirs_seen:
dirs_seen[dirent] = True
self.directories.append(dirent)
entry = name.replace(dirent, '')
entry_match = dir_regex.match(entry)
if entry_match:
subdir = entry_match.group(1) + '/'
if subdir in subdirs_seen:
continue
subdirs_seen[subdir] = True
dir_entries[dirent].append(subdir)
else:
dir_entries[dirent].append(entry)
else:
self.subtree_types.append(self.types[idx])
self.subtree_sha1s.append(self.sha1s[idx])
self.subtree_names.append(name)
def add_or_remove(self, *to_process):
"""Invokes 'git add' to index the filenames in to_process that exist
and 'git rm' for those that do not exist."""
if not to_process:
return 'No files to add or remove.'
to_add = []
to_remove = []
for filename in to_process:
if os.path.exists(filename):
to_add.append(filename)
output = self.git.add(v=True, *to_add)
if len(to_add) == len(to_process):
# to_process only contained unremoved files --
# short-circuit the removal checks
return output
# Process files to remote
for filename in to_process:
if not os.path.exists(filename):
to_remove.append(filename)
output + '\n\n' + self.git.rm(*to_remove)
def get_editor(self):
return self.global_gui_editor
def get_diffeditor(self):
return self.global_gui_diffeditor
def get_history_browser(self):
return self.global_gui_historybrowser
def remember_gui_settings(self):
return self.global_cola_savewindowsettings
def save_at_exit(self):
return self.global_cola_saveatexit
def get_tree_node(self, idx):
return (self.get_types()[idx],
self.get_sha1s()[idx],
self.get_names()[idx] )
def get_subtree_node(self, idx):
return (self.get_subtree_types()[idx],
self.get_subtree_sha1s()[idx],
self.get_subtree_names()[idx] )
def get_all_branches(self):
return (self.get_local_branches() + self.get_remote_branches())
def set_remote(self, remote):
if not remote: return
self.set_param('remote', remote)
branches = utils.grep('%s/\S+$' % remote,
self.branch_list(remote=True),
squash=False)
self.set_remote_branches(branches)
def add_signoff(self,*rest):
"""Adds a standard Signed-off by: tag to the end
of the current commit message."""
msg = self.get_commitmsg()
signoff =('\n\nSigned-off-by: %s <%s>\n'
% (self.get_local_user_name(), self.get_local_user_email()))
if signoff not in msg:
self.set_commitmsg(msg + signoff)
def apply_diff(self, filename):
return self.git.apply(filename, index=True, cached=True)
def apply_diff_to_worktree(self, filename):
return self.git.apply(filename)
def load_commitmsg(self, path):
file = open(path, 'r')
contents = file.read()
file.close()
self.set_commitmsg(contents)
def get_prev_commitmsg(self,*rest):
"""Queries git for the latest commit message and sets it in
self.commitmsg."""
commit_msg = []
commit_lines = self.git.show('HEAD').split('\n')
for idx, msg in enumerate(commit_lines):
if idx < 4: continue
msg = msg.lstrip()
if msg.startswith('diff --git'):
commit_msg.pop()
break
commit_msg.append(msg)
self.set_commitmsg('\n'.join(commit_msg).rstrip())
def update_status(self):
# This allows us to defer notification until the
# we finish processing data
notify_enabled = self.get_notify()
self.set_notify(False)
# Reset the staged and unstaged model lists
# NOTE: the model's unstaged list is used to
# hold both modified and untracked files.
self.staged = []
self.modified = []
self.untracked = []
# Read git status items
(staged_items,
modified_items,
untracked_items) = self.parse_status()
# Gather items to be committed
for staged in staged_items:
if staged not in self.get_staged():
self.add_staged(staged)
# Gather unindexed items
for modified in modified_items:
if modified not in self.get_modified():
self.add_modified(modified)
# Gather untracked items
for untracked in untracked_items:
if untracked not in self.get_untracked():
self.add_untracked(untracked)
self.set_currentbranch(self.current_branch())
self.set_unstaged(self.get_modified() + self.get_untracked())
self.set_remotes(self.git.remote().splitlines())
self.set_remote_branches(self.branch_list(remote=True))
self.set_local_branches(self.branch_list(remote=False))
self.set_tags(self.git.tag().splitlines())
self.set_revision('')
self.set_local_branch('')
self.set_remote_branch('')
# Re-enable notifications and emit changes
self.set_notify(notify_enabled)
self.notify_observers('staged','unstaged')
def delete_branch(self, branch):
return self.git.branch(branch, D=True)
def get_revision_sha1(self, idx):
return self.get_revisions()[idx]
def apply_font_size(self, param, default):
old_font = self.get_param(param)
if not old_font:
old_font = default
size = self.get_param(param+'_size')
props = old_font.split(',')
props[1] = str(size)
new_font = ','.join(props)
self.set_param(param, new_font)
def read_font_size(self, param, new_font):
new_size = int(new_font.split(',')[1])
self.set_param(param, new_size)
def get_commit_diff(self, sha1):
commit = self.git.show(sha1)
first_newline = commit.index('\n')
if commit[first_newline+1:].startswith('Merge:'):
return (commit + '\n\n'
+ self.diff_helper(commit=sha1,
cached=False,
suppress_header=False))
else:
return commit
def get_diff_and_status(self, idx, staged=True):
if staged:
filename = self.get_staged()[idx]
if os.path.exists(filename):
status = 'Staged for commit'
else:
status = 'Staged for removal'
diff = self.diff_helper(filename=filename,
cached=True)
else:
filename = self.get_unstaged()[idx]
if os.path.isdir(filename):
status = 'Untracked directory'
diff = '\n'.join(os.listdir(filename))
elif filename in self.get_modified():
status = 'Modified, not staged'
diff = self.diff_helper(filename=filename,
cached=False)
else:
status = 'Untracked, not staged'
file_type = utils.run_cmd('file', '-b', filename)
if 'binary' in file_type or 'data' in file_type:
diff = utils.run_cmd('hexdump', '-C', filename)
else:
if os.path.exists(filename):
file = open(filename, 'r')
diff = file.read()
file.close()
else:
diff = ''
return diff, status
def stage_modified(self):
output = self.git.add(self.get_modified())
self.update_status()
return output
def stage_untracked(self):
output = self.git.add(self.get_untracked())
self.update_status()
return output
def reset(self, *items):
output = self.git.reset('--', *items)
self.update_status()
return output
def unstage_all(self):
self.git.reset('--', *self.get_staged())
self.update_status()
def save_gui_settings(self):
self.config_set('cola.geometry', utils.get_geom(), local=False)
def config_set(self, key=None, value=None, local=True):
if key and value is not None:
# git config category.key value
strval = str(value)
if type(value) is bool:
# git uses "true" and "false"
strval = strval.lower()
if local:
argv = [ key, strval ]
else:
argv = [ '--global', key, strval ]
return self.git.config(*argv)
else:
msg = "oops in config_set(key=%s,value=%s,local=%s"
raise Exception(msg % (key, value, local))
def config_dict(self, local=True):
"""parses the lines from git config --list into a dictionary"""
kwargs = {
'list': True,
'global': not local,
}
config_lines = self.git.config(**kwargs).splitlines()
newdict = {}
for line in config_lines:
k, v = line.split('=', 1)
k = k.replace('.','_') # git -> model
if v == 'true' or v == 'false':
v = bool(eval(v.title()))
try:
v = int(eval(v))
except:
pass
newdict[k]=v
return newdict
def commit_with_msg(self, msg, amend=False):
"""Creates a git commit."""
if not msg.endswith('\n'):
msg += '\n'
# Sure, this is a potential "security risk," but if someone
# is trying to intercept/re-write commit messages on your system,
# then you probably have bigger problems to worry about.
tmpfile = self.get_tmp_filename()
# Create the commit message file
file = open(tmpfile, 'w')
file.write(msg)
file.close()
# Run 'git commit'
output = self.git.commit(F=tmpfile, amend=amend)
os.unlink(tmpfile)
return ('git commit -F %s --amend %s\n\n%s'
% ( tmpfile, amend, output ))
def diffindex(self):
return self.git.diff(unified=self.diff_context,
stat=True,
cached=True)
def get_tmp_dir(self):
return os.environ.get('TMP', os.environ.get('TMPDIR', '/tmp'))
def get_tmp_file_pattern(self):
return os.path.join(self.get_tmp_dir(), '*.git.%s.*' % os.getpid())
def get_tmp_filename(self, prefix=''):
# Allow TMPDIR/TMP with a fallback to /tmp
basename = (prefix+'.git.%s.%s'
% (os.getpid(), time.time())).replace(os.sep, '-')
return os.path.join(self.get_tmp_dir(), basename)
def log_helper(self, all=False):
"""
Returns a pair of parallel arrays listing the revision sha1's
and commit summaries.
"""
revs = []
summaries = []
regex = REV_LIST_REGEX
output = self.git.log(pretty='oneline', all=all)
for line in output.splitlines():
match = regex.match(line)
if match:
revs.append(match.group(1))
summaries.append(match.group(2))
return (revs, summaries)
def parse_rev_list(self, raw_revs):
revs = []
for line in raw_revs.splitlines():
match = REV_LIST_REGEX.match(line)
if match:
rev_id = match.group(1)
summary = match.group(2)
revs.append((rev_id, summary,))
return revs
def rev_list_range(self, start, end):
range = '%s..%s' % (start, end)
raw_revs = self.git.rev_list(range, pretty='oneline')
return self.parse_rev_list(raw_revs)
def diff_helper(self,
commit=None,
branch=None,
filename=None,
color=False,
cached=True,
with_diff_header=False,
suppress_header=True,
reverse=False):
"Invokes git diff on a filepath."
argv = []
if commit:
argv.append('%s^..%s' % (commit, commit))
elif branch:
argv.append(branch)
if filename:
argv.append('--')
if type(filename) is list:
argv.extend(filename)
else:
argv.append(filename)
diffoutput = self.git.diff(R=reverse,
color=color,
cached=cached,
patch_with_raw=True,
unified=self.diff_context,
with_raw_output=True,
*argv)
diff = diffoutput.splitlines()
output = StringIO()
start = False
del_tag = 'deleted file mode '
headers = []
deleted = cached and not os.path.exists(filename)
for line in diff:
if not start and '@@ ' in line and ' @@' in line:
start = True
if start or(deleted and del_tag in line):
output.write(line + '\n')
else:
if with_diff_header:
headers.append(line)
elif not suppress_header:
output.write(line + '\n')
result = output.getvalue()
output.close()
if with_diff_header:
return('\n'.join(headers), result)
else:
return result
def git_repo_path(self, *subpaths):
paths = [ self.git.get_git_dir() ]
paths.extend(subpaths)
return os.path.realpath(os.path.join(*paths))
def get_merge_message_path(self):
for file in ('MERGE_MSG', 'SQUASH_MSG'):
path = self.git_repo_path(file)
if os.path.exists(path):
return path
return None
def get_merge_message(self):
return self.git.fmt_merge_msg('--file',
self.git_repo_path('FETCH_HEAD'))
def abort_merge(self):
# Reset the worktree
output = self.git.read_tree('HEAD', reset=True, u=True, v=True)
# remove MERGE_HEAD
merge_head = self.git_repo_path('MERGE_HEAD')
if os.path.exists(merge_head):
os.unlink(merge_head)
# remove MERGE_MESSAGE, etc.
merge_msg_path = self.get_merge_message_path()
while merge_msg_path:
os.unlink(merge_msg_path)
merge_msg_path = self.get_merge_message_path()
def parse_status(self):
"""RETURNS: A tuple of staged, unstaged and untracked file lists.
"""
def eval_path(path):
"""handles quoted paths."""
if path.startswith('"') and path.endswith('"'):
return eval(path)
else:
return path
MODIFIED_TAG = '# Changed but not updated:'
UNTRACKED_TAG = '# Untracked files:'
RGX_RENAMED = re.compile('(#\trenamed:\s+|#\tcopied:\s+)'
'(.*?)\s->\s(.*)')
RGX_MODIFIED = re.compile('(#\tmodified:\s+'
'|#\tnew file:\s+'
'|#\tdeleted:\s+)')
staged = []
unstaged = []
untracked = []
STAGED_MODE = 0
UNSTAGED_MODE = 1
UNTRACKED_MODE = 2
current_dest = staged
mode = STAGED_MODE
for status_line in self.git.status().splitlines():
if status_line == MODIFIED_TAG:
mode = UNSTAGED_MODE
current_dest = unstaged
continue
elif status_line == UNTRACKED_TAG:
mode = UNTRACKED_MODE
current_dest = untracked
continue
# Staged/unstaged modified/renamed/deleted files
if mode is STAGED_MODE or mode is UNSTAGED_MODE:
match = RGX_MODIFIED.match(status_line)
if match:
tag = match.group(0)
filename = status_line.replace(tag, '')
current_dest.append(eval_path(filename))
continue
match = RGX_RENAMED.match(status_line)
if match:
oldname = match.group(2)
newname = match.group(3)
current_dest.append(eval_path(oldname))
current_dest.append(eval_path(newname))
continue
# Untracked files
elif mode is UNTRACKED_MODE:
if status_line.startswith('#\t'):
current_dest.append(eval_path(status_line[2:]))
return( staged, unstaged, untracked )
def reset_helper(self, *args, **kwargs):
return self.git.reset('--', *args, **kwargs)
def remote_url(self, name):
return self.git.config('remote.%s.url' % name, get=True)
def get_remote_args(self, remote,
local_branch='', remote_branch='',
ffwd=True, tags=False):
if ffwd:
branch_arg = '%s:%s' % ( remote_branch, local_branch )
else:
branch_arg = '+%s:%s' % ( remote_branch, local_branch )
args = [remote]
if local_branch and remote_branch:
args.append(branch_arg)
kwargs = {
"with_extended_output": True,
"tags": tags
}
return (args, kwargs)
def fetch_helper(self, *args, **kwargs):
"""
Fetches remote_branch to local_branch only if
remote_branch and local_branch are both supplied.
If either is ommitted, "git fetch <remote>" is performed instead.
Returns (status,output)
"""
args, kwargs = self.get_remote_args(*args, **kwargs)
(status, stdout, stderr) = self.git.fetch(v=True, *args, **kwargs)
return (status, stdout + stderr)
def push_helper(self, *args, **kwargs):
"""
Pushes local_branch to remote's remote_branch only if
remote_branch and local_branch both are supplied.
If either is ommitted, "git push <remote>" is performed instead.
Returns (status,output)
"""
args, kwargs = self.get_remote_args(*args, **kwargs)
(status, stdout, stderr) = self.git.push(*args, **kwargs)
return (status, stdout + stderr)
def pull_helper(self, *args, **kwargs):
"""
Pushes branches. If local_branch or remote_branch is ommitted,
"git pull <remote>" is performed instead of
"git pull <remote> <remote_branch>:<local_branch>
Returns (status,output)
"""
args, kwargs = self.get_remote_args(*args, **kwargs)
(status, stdout, stderr) = self.git.pull(v=True, *args, **kwargs)
return (status, stdout + stderr)
def parse_ls_tree(self, rev):
"""Returns a list of(mode, type, sha1, path) tuples."""
lines = self.git.ls_tree(rev, r=True).splitlines()
output = []
regex = re.compile('^(\d+)\W(\w+)\W(\w+)[ \t]+(.*)$')
for line in lines:
match = regex.match(line)
if match:
mode = match.group(1)
objtype = match.group(2)
sha1 = match.group(3)
filename = match.group(4)
output.append((mode, objtype, sha1, filename,) )
return output
def format_patch_helper(self, to_export, revs, output='patches'):
"""writes patches named by to_export to the output directory."""
outlines = []
cur_rev = to_export[0]
cur_master_idx = revs.index(cur_rev)
patches_to_export = [ [cur_rev] ]
patchset_idx = 0
# Group the patches into continuous sets
for idx, rev in enumerate(to_export[1:]):
# Limit the search to the current neighborhood for efficiency
master_idx = revs[ cur_master_idx: ].index(rev)
master_idx += cur_master_idx
if master_idx == cur_master_idx + 1:
patches_to_export[ patchset_idx ].append(rev)
cur_master_idx += 1
continue
else:
patches_to_export.append([ rev ])
cur_master_idx = master_idx
patchset_idx += 1
# Export each patchsets
for patchset in patches_to_export:
cmdoutput = self.export_patchset(patchset[0],
patchset[-1],
output="patches",
n=len(patchset) > 1,
thread=True,
patch_with_stat=True)
outlines.append(cmdoutput)
return '\n'.join(outlines)
def export_patchset(self, start, end, output="patches", **kwargs):
revarg = '%s^..%s' % (start, end)
return self.git.format_patch("-o", output, revarg, **kwargs)
def current_branch(self):
"""Parses 'git branch' to find the current branch."""
branches = self.git.branch().splitlines()
for branch in branches:
if branch.startswith('* '):
return branch.lstrip('* ')
return 'Detached HEAD'
def create_branch(self, name, base, track=False):
"""Creates a branch starting from base. Pass track=True
to create a remote tracking branch."""
return self.git.branch(name, base, track=track)
def cherry_pick_list(self, revs, **kwargs):
"""Cherry-picks each revision into the current branch.
Returns a list of command output strings (1 per cherry pick)"""
if not revs:
return []
cherries = []
for rev in revs:
cherries.append(self.git.cherry_pick(rev, **kwargs))
return '\n'.join(cherries)
def parse_stash_list(self, revids=False):
"""Parses "git stash list" and returns a list of stashes."""
stashes = self.stash("list").splitlines()
if revids:
return [ s[:s.index(':')] for s in stashes ]
else:
return [ s[s.index(':')+1:] for s in stashes ]
+40
View File
@@ -0,0 +1,40 @@
#!/usr/bin/env python
from pprint import pformat
class Observer(object):
"""
Observers receive notify(*attributes) messages from their
subjects whenever new data arrives. This notify() message signifies
that an observer should update its internal state/view.
"""
def __init__(self, model):
self.model = model
self.model.add_observer(self)
self.__debug = False
def __del__(self):
self.model.remove_observer(self)
def set_debug(self, enabled):
self.__debug = enabled
def notify(self, *attributes):
"""Called by the model to notify Observers about changes."""
# We can be notified about multiple attribute changes at once
model = self.model
for attr in attributes:
notify = model.get_notify()
model.set_notify(False) # NOTIFY OFF
value = model.get_param(attr)
self.subject_changed(attr, value)
model.set_notify(notify) # NOTIFY ON
def subject_changed(self, attr, value):
"""This method handles updating of the observer/UI.
This must be implemented in each concrete observer class."""
msg = 'Concrete Observers must override subject_changed().'
raise NotImplementedError, msg
+207
View File
@@ -0,0 +1,207 @@
#!/usr/bin/env python
from PyQt4.QtCore import Qt
from PyQt4.QtCore import QObject
from PyQt4.QtCore import SIGNAL
from PyQt4.QtCore import QDate
from PyQt4.QtGui import QDateEdit
from PyQt4.QtGui import QSpinBox
from PyQt4.QtGui import QPixmap
from PyQt4.QtGui import QTextEdit
from PyQt4.QtGui import QLineEdit
from PyQt4.QtGui import QListWidget
from PyQt4.QtGui import QCheckBox
from PyQt4.QtGui import QFontComboBox
from PyQt4.QtGui import QAbstractButton
from PyQt4.QtGui import QSplitter
from PyQt4.QtGui import QAction
from cola.observer import Observer
class QObserver(Observer, QObject):
def __init__(self, model, view, *args, **kwargs):
Observer.__init__(self, model)
QObject.__init__(self)
self.view = view
self.__actions = {}
self.__callbacks = {}
self.__model_to_view = {}
self.__view_to_model = {}
self.__connected = []
# Call the subclass's startup routine
self.init(model, view, *args, **kwargs)
def init(self, model, view, *args, **kwargs):
pass
def SLOT(self, *args):
"""Default slot to handle all Qt callbacks.
This method delegates to callbacks from add_signals."""
widget = self.sender()
sender = str(widget.objectName())
if sender in self.__callbacks:
self.__callbacks[sender](*args)
elif sender in self.__view_to_model:
model = self.model
model_param = self.__view_to_model[sender]
if isinstance(widget, QTextEdit):
value = unicode(widget.toPlainText())
model.set_param(model_param, value,
notify=False)
elif isinstance(widget, QLineEdit):
value = unicode(widget.text())
model.set_param(model_param, value)
elif isinstance(widget, QCheckBox):
model.set_param(model_param, widget.isChecked())
elif isinstance(widget, QSpinBox):
model.set_param(model_param, widget.value())
elif isinstance(widget, QFontComboBox):
value = unicode(widget.currentFont().toString())
model.set_param(model_param, value)
elif isinstance(widget, QDateEdit):
fmt = Qt.ISODate
value = str(widget.date().toString(fmt))
model.set_param(model_param, value)
elif isinstance(widget, QListWidget):
pass
else:
print("SLOT(): Unknown widget:", sender, widget)
def connect(self, obj, signal_str, *args):
"""Convenience function so that subclasses do not have
to import QtCore.SIGNAL."""
signal = signal_str
if type(signal) is str:
signal = SIGNAL(signal)
return QObject.connect(obj, signal, *args)
def add_signals(self, signal_str, *objects):
"""Connects object's signal to the QObserver."""
for obj in objects:
self.connect(obj, signal_str, self.SLOT)
def add_callbacks(self, **callbacks):
"""Registers callbacks that are called in response to GUI events."""
for sender, callback in callbacks.iteritems():
self.__callbacks[sender] = callback
self.autoconnect(getattr(self.view, sender))
def add_observables(self, *params):
"""This method assumes that widgets and model params
share the same name."""
for param in params:
self.model_to_view(param, getattr(self.view, param))
def model_to_view(self, model_param, *widgets):
"""Binds model params to qt widgets(model->view)"""
self.__model_to_view[model_param] = widgets
for w in widgets:
view = str(w.objectName())
self.__view_to_model[view] = model_param
self.__autoconnect(w)
def autoconnect(self, *widgets):
for w in widgets:
self.__autoconnect(w)
def __autoconnect(self, widget):
"""Automagically connects Qt widgets to QObserver.SLOT"""
if widget in self.__connected: return
self.__connected.append(widget)
if isinstance(widget, QTextEdit):
self.add_signals('textChanged()', widget)
elif isinstance(widget, QLineEdit):
self.add_signals('textChanged(const QString&)', widget)
elif isinstance(widget, QListWidget):
self.add_signals('itemSelectionChanged()', widget)
self.add_signals('itemClicked(QListWidgetItem *)', widget)
self.add_signals('itemDoubleClicked(QListWidgetItem*)', widget)
elif isinstance(widget, QAbstractButton):
self.add_signals('released()', widget)
elif isinstance(widget, QAction):
self.add_signals('triggered()', widget)
elif isinstance(widget, QCheckBox):
self.add_signals('stateChanged(int)', widget)
elif isinstance(widget, QSpinBox):
self.add_signals('valueChanged(int)', widget)
elif isinstance(widget, QFontComboBox):
self.add_signals('currentFontChanged(const QFont&)', widget)
elif isinstance(widget, QSplitter):
self.add_signals('splitterMoved(int,int)', widget)
elif isinstance(widget, QDateEdit):
self.add_signals('dateChanged(const QDate&)', widget)
else:
raise Exception('Asked to connect unknown widget:\n\t%s => %s'
% (type(widget), str(widget.objectName())))
def add_actions(self, **kwargs):
"""Register view actions that are called in response to
view changes.(view->model)"""
for model_param, callback in kwargs.iteritems():
if type(callback) is list:
self.__actions[model_param] = callback
else:
self.__actions[model_param] = [callback]
def subject_changed(self, param, value):
"""Sends a model param to the view(model->view)"""
if param in self.__model_to_view:
notify = self.model.get_notify()
self.model.set_notify(False)
for widget in self.__model_to_view[param]:
if isinstance(widget, QSpinBox):
widget.setValue(value)
elif isinstance(widget, QPixmap):
widget.load(value)
elif isinstance(widget, QTextEdit):
widget.setText(value)
elif isinstance(widget, QLineEdit):
widget.setText(value)
elif isinstance(widget, QListWidget):
widget.clear()
for i in value:
widget.addItem(i)
elif isinstance(widget, QCheckBox):
widget.setChecked(value)
elif isinstance(widget, QFontComboBox):
font = widget.currentFont()
font.fromString(value)
elif isinstance(widget, QDateEdit):
if not value: return
fmt = Qt.ISODate
date = QDate.fromString(value, fmt)
if date:
widget.setDate(date)
else:
print('subject_changed(): Unknown widget:',
str(widget.objectName()), widget, value)
self.model.set_notify(notify)
if param not in self.__actions:
return
widgets = []
if param in self.__model_to_view:
for widget in self.__model_to_view[param]:
widgets.append(widget)
# Call the model callback w/ the view's widgets as the args
for action in self.__actions[param]:
action(*widgets)
def refresh_view(self, *params):
if not params:
params= tuple(self.__model_to_view.keys()
+self.__actions.keys())
notified = []
for param in params:
if param not in notified:
notified.append(param)
self.model.notify_observers(*notified)
+171
View File
@@ -0,0 +1,171 @@
import os
from PyQt4 import QtCore
from PyQt4 import QtGui
from PyQt4.QtGui import QClipboard
from PyQt4.QtGui import QFileDialog
from PyQt4.QtGui import QIcon
from PyQt4.QtGui import QListWidgetItem
from PyQt4.QtGui import QMessageBox
from cola import utils
LOGGER = None
def log(output, quiet=True, doraise=False):
if not LOGGER: return
LOGGER.log(output)
if quiet: return
LOGGER.show()
if not doraise: return
raise_logger()
def raise_logger():
LOGGER.raise_()
def input(msg, title=None):
if title is None:
title = msg
parent = QtGui.qApp.activeWindow()
result = QtGui.QInputDialog.getText(parent, msg, title)
return (unicode(result[0]), result[1])
def close_log_window():
LOGGER.hide()
LOGGER.done(0)
def show_output(output, **kwargs):
if not output: return
log(output, quiet=False)
def toggle_log_window():
if not LOGGER: return
if LOGGER.isVisible():
LOGGER.hide()
else:
LOGGER.show()
LOGGER.raise_()
def create_listwidget_item(text, filename):
icon = QIcon(filename)
item = QListWidgetItem()
item.setIcon(icon)
item.setText(text)
return item
def information(title, message=None):
"""Launches a QMessageBox information with the
provided title and message."""
if message is None:
message = title
QMessageBox.information(QtGui.qApp.activeWindow(), title, message)
def get_selected_row(list_widget):
"""Returns a(row_number, is_selected) tuple for a QListWidget."""
row = list_widget.currentRow()
item = list_widget.item(row)
selected = item is not None and item.isSelected()
return(row, selected)
def get_selection_list(listwidget, items):
"""Returns an array of model items that correspond to
the selected QListWidget indices."""
selected = []
itemcount = listwidget.count()
widgetitems = [ listwidget.item(idx) for idx in range(itemcount) ]
for item, widgetitem in zip(items, widgetitems):
if widgetitem.isSelected():
selected.append(item)
return selected
def get_selected_item(list_widget, items):
row, selected = get_selected_row(list_widget)
if selected and row < len(items):
return items[row]
else:
return None
def open_dialog(parent, title, filename=None):
qstr = QFileDialog.getOpenFileName(parent, parent.tr(title), filename)
return unicode(qstr)
def opendir_dialog(parent, title, directory):
flags = QtGui.QFileDialog.ShowDirsOnly | QtGui.QFileDialog.DontResolveSymlinks
qstr = QFileDialog.getExistingDirectory(parent, directory,
parent.tr(title),
flags)
return unicode(qstr)
def save_dialog(parent, title, filename=None):
qstr = QFileDialog.getSaveFileName(parent, parent.tr(title), filename)
return unicode(qstr)
def dir_dialog(parent, title, directory):
directory = QFileDialog.getExistingDirectory(parent, title, directory)
return unicode(directory)
def get_qicon(filename):
icon = utils.get_icon(filename)
return QIcon(icon)
def question(parent, title, message, default=True):
"""Launches a QMessageBox question with the provided title and message.
Passing "default=False" will make "No" the default choice."""
yes = QMessageBox.Yes
no = QMessageBox.No
buttons = yes | no
if default:
default = yes
else:
default = no
result = QMessageBox.question(parent, title, message, buttons, default)
return result == QMessageBox.Yes
def set_clipboard(text):
QtGui.qApp.clipboard().setText(text, QClipboard.Clipboard)
QtGui.qApp.clipboard().setText(text, QClipboard.Selection)
def add_items(widget, items):
for item in items: widget.addItem(item)
def set_items(widget, items):
widget.clear()
add_items(widget, items)
def tr(txt):
trtext = unicode(QtGui.qApp.tr(txt))
if trtext.endswith('@@verb'):
trtext = trtext.replace('@@verb','')
if trtext.endswith('@@noun'):
trtext = trtext.replace('@@noun','')
return trtext
def create_item(filename, staged, untracked=False):
"""Given a filename, return a QListWidgetItem suitable
for adding to a QListWidget. "staged" and "untracked"
controls whether to use the appropriate icons."""
if staged:
if os.path.exists(filename):
icon_file = utils.get_icon('staged.png')
else:
icon_file = utils.get_icon('removed.png')
elif untracked:
icon_file = utils.get_icon('untracked.png')
else:
icon_file = utils.get_file_icon(filename)
return create_listwidget_item(filename, icon_file)
def create_txt_item(txt):
item = QListWidgetItem()
item.setText(txt)
return item
def update_listwidget(widget, items, staged=True,
untracked=False, append=False):
"""Populate a QListWidget with custom icon items."""
if not append: widget.clear()
add_items(widget, [ create_item(i, staged, untracked) for i in items ])
def set_listwidget_strings(widget, items):
widget.clear()
add_items(widget, [ create_txt_item(i) for i in items ])
+29
View File
@@ -0,0 +1,29 @@
#!/usr/bin/env python
HAS_SIMPLEJSON = False
try:
import simplejson
HAS_SIMPLEJSON = True
except ImportError:
pass
import os
import user
from cola.model import Model
class SettingsModel(Model):
def init(self):
self.create( bookmarks = [] )
if not HAS_SIMPLEJSON:
return
settings = self.path()
if os.path.exists(settings):
self.load(settings)
def path(self):
return os.path.join(user.home, '.cola')
def save_all_settings(self):
if not HAS_SIMPLEJSON:
return
self.save(self.path())
Executable
+169
View File
@@ -0,0 +1,169 @@
#!/usr/bin/python
import re
from PyQt4.QtCore import Qt
from PyQt4.QtGui import QFont
from PyQt4.QtGui import QSyntaxHighlighter
from PyQt4.QtGui import QTextCharFormat
from PyQt4.QtGui import QColor
class GenericSyntaxHighligher(QSyntaxHighlighter):
def __init__(self, doc):
QSyntaxHighlighter.__init__(self, doc)
self.__rules = []
FINAL_STR = '__FINAL__:'
def final(self, pattern=''):
"""
Denotes that a pattern is the final pattern that should
be matched. If this pattern matches no other formats
will be applied, even if they would have matched.
"""
return GenericSyntaxHighligher.FINAL_STR + pattern
def create_rules(self, *rules):
if len(rules) % 2:
raise Exception('create_rules requires an even '
'number of arguments.')
for idx, rule in enumerate(rules):
if idx % 2:
continue
formats = rules[idx+1]
terminal = rule.startswith(self.final())
if terminal:
rule = rule[len(self.final()):]
regex = re.compile(rule)
self.__rules.append((regex, formats, terminal,))
def get_formats(self, line):
matched = []
for regex, fmts, terminal in self.__rules:
match = regex.match(line)
if match:
matched.append([match, fmts])
if terminal: return matched
return matched
def mkformat(self, fg=None, bg=None, bold=False):
format = QTextCharFormat()
if fg: format.setForeground(fg)
if bg: format.setBackground(bg)
if bold: format.setFontWeight(QFont.Bold)
return format
def highlightBlock(self, qstr):
ascii = qstr.toAscii().data()
if not ascii: return
formats = self.get_formats(ascii)
if not formats: return
for match, fmts in formats:
start = match.start()
end = match.end()
groups = match.groups()
# No groups in the regex, assume this is a single rule
# that spans the entire line
if not groups:
self.setFormat(0, len(ascii), fmts)
continue
# Groups exist, rule is a tuple corresponding to group
for grpidx, group in enumerate(groups):
# allow empty matches
if not group: continue
# allow None as a no-op format
length = len(group)
if fmts[grpidx]:
self.setFormat(start, start+length,
fmts[grpidx])
start += length
class DiffSyntaxHighlighter(GenericSyntaxHighligher):
def __init__(self, doc,whitespace=True):
GenericSyntaxHighligher.__init__(self,doc)
diffstat = self.mkformat(Qt.blue, bold=True)
diffstat_add = self.mkformat(Qt.darkGreen, bold=True)
diffstat_remove = self.mkformat(Qt.red, bold=True)
bg_green = QColor(Qt.green)
bg_green.setAlpha(128)
bg_red = QColor(Qt.red)
bg_red.setAlpha(128)
diff_begin = self.mkformat(Qt.darkCyan, bold=True)
diff_head = self.mkformat(Qt.darkYellow)
diff_add = self.mkformat(bg=bg_green)
diff_remove = self.mkformat(bg=bg_red)
if whitespace:
bad_ws = self.mkformat(Qt.black, Qt.red)
# We specify the whitespace rule last so that it is
# applied after the diff addition/removal rules.
# The rules for the header
diff_bgn_rgx = self.final('^@@|^\+\+\+|^---')
diff_hd1_rgx = self.final('^diff --git')
diff_hd2_rgx = self.final('^index \S+\.\.\S+')
diff_hd3_rgx = self.final('^new file mode')
diff_add_rgx = self.final('^\+')
diff_rmv_rgx = self.final('^-')
diff_sts_rgx = ('(.+\|.+?)(\d+)(.+?)([\+]*?)([-]*?)$')
diff_sum_rgx = ('(\s+\d+ files changed[^\d]*)'
'(:?\d+ insertions[^\d]*)'
'(:?\d+ deletions.*)$')
self.create_rules(diff_bgn_rgx, diff_begin,
diff_hd1_rgx, diff_head,
diff_hd2_rgx, diff_head,
diff_hd3_rgx, diff_head,
diff_add_rgx, diff_add,
diff_rmv_rgx, diff_remove,
diff_sts_rgx, (None, diffstat,
None, diffstat_add,
diffstat_remove),
diff_sum_rgx, (diffstat,
diffstat_add,
diffstat_remove))
if whitespace:
self.create_rules('(..*?)(\s+)$', (None, bad_ws))
class LogSyntaxHighlighter(GenericSyntaxHighligher):
def __init__(self, doc):
GenericSyntaxHighligher.__init__(self,doc)
blue = self.mkformat(Qt.blue, bold=True)
black = self.mkformat(Qt.black, bold=True)
dark_cyan = self.mkformat(Qt.darkCyan, bold=True)
blue_black_rgx = '^([^:]+:)(.*)$'
dark_cyan_rgx = self.final('^\w{3}\W+\w{3}\W+\d+\W+'
'[:0-9]+\W+\d{4}$')
self.create_rules(dark_cyan_rgx, dark_cyan,
blue_black_rgx, (blue, black))
if __name__ == '__main__':
import sys
from PyQt4 import QtCore, QtGui
class SyntaxTestDialog(QtGui.QDialog):
def __init__(self, parent):
QtGui.QDialog.__init__(self, parent)
self.setupUi(self)
def setupUi(self, dialog):
dialog.resize(QtCore.QSize(QtCore.QRect(0,0,720,512).size()).expandedTo(dialog.minimumSizeHint()))
self.vboxlayout = QtGui.QVBoxLayout(dialog)
self.vboxlayout.setObjectName('vboxlayout')
self.output_text = QtGui.QTextEdit(dialog)
font = QtGui.QFont()
font.setFamily('Monospace')
font.setPointSize(13)
self.output_text.setFont(font)
self.output_text.setAcceptDrops(False)
self.vboxlayout.addWidget(self.output_text)
DiffSyntaxHighlighter(self.output_text.document())
app = QtGui.QApplication(sys.argv)
dialog = SyntaxTestDialog(app.activeWindow())
dialog.show()
dialog.exec_()
+440
View File
@@ -0,0 +1,440 @@
#!/usr/bin/env python
import sys
import os
import re
import subprocess
from cStringIO import StringIO
import defaults
PREFIX = os.path.realpath(os.path.dirname(os.path.dirname(sys.argv[0])))
QMDIR = os.path.join(PREFIX, 'share', 'cola', 'qm')
ICONSDIR = os.path.join(PREFIX, 'share', 'cola', 'icons')
KNOWN_FILE_TYPES = {
'ascii c': 'c.png',
'python': 'script.png',
'ruby': 'script.png',
'shell': 'script.png',
'perl': 'script.png',
'java': 'script.png',
'assembler': 'binary.png',
'binary': 'binary.png',
'byte': 'binary.png',
'image': 'image.png',
}
def run_cmd(*command):
"""
Runs a *command argument list and returns the output.
e.g. run_cmd("echo", "hello", "world")
"""
# Start the process
proc = subprocess.Popen(command, stdout = subprocess.PIPE)
# Wait for the process to return
stdout_value = proc.stdout.read()
proc.stdout.close()
proc.wait()
status = proc.poll()
# Strip off trailing whitespace by default
return stdout_value.rstrip()
def get_qm_for_locale(locale):
regex = re.compile(r'([^\.])+\..*$')
match = regex.match(locale)
if match:
locale = match.group(1)
basename = locale.split('_')[0]
return os.path.join(QMDIR, basename +'.qm')
def ident_file_type(filename):
"""Returns an icon based on the contents of filename."""
if os.path.exists(filename):
fileinfo = run_cmd('file','-b',filename)
for filetype, iconname in KNOWN_FILE_TYPES.iteritems():
if filetype in fileinfo.lower():
return iconname
else:
return 'removed.png'
# Fallback for modified files of an unknown type
return 'generic.png'
def get_file_icon(filename):
"""
Returns the full path to an icon file corresponding to
filename"s contents.
"""
icon_file = ident_file_type(filename)
return get_icon(icon_file)
def get_icon(icon_file):
return os.path.join(ICONSDIR, icon_file)
def fork(*args):
if os.name in ('nt', 'dos'):
for path in os.pathsep.split(os.environ["PATH"]):
file = os.path.join(path, args[0]) + ".exe"
try:
return os.spawnv(os.P_NOWAIT, file, (file,) + args[1:])
except os.error:
pass
raise IOError('cannot find executable: %s' % program)
else:
argv = map(shell_quote, args)
return os.system(' '.join(argv) + '&')
# c = a - b
def sublist(a,b):
c = []
for item in a:
if item not in b:
c.append(item)
return c
__grep_cache = {}
def grep(pattern, items, squash=True):
isdict = type(items) is dict
if pattern in __grep_cache:
regex = __grep_cache[pattern]
else:
regex = __grep_cache[pattern] = re.compile(pattern)
matched = []
matchdict = {}
for item in items:
match = regex.match(item)
if not match: continue
groups = match.groups()
if not groups:
subitems = match.group(0)
else:
if len(groups) == 1:
subitems = groups[0]
else:
subitems = list(groups)
if isdict:
matchdict[item] = items[item]
else:
matched.append(subitems)
if isdict:
return matchdict
else:
if squash and len(matched) == 1:
return matched[0]
else:
return matched
def basename(path):
"""Avoid os.path.basename because we are explicitly
parsing git"s output, which contains /"s regardless
of platform (a.t.m.)
"""
base_regex = re.compile('(.*?/)?([^/]+)$')
match = base_regex.match(path)
if match:
return match.group(2)
else:
return pathstr
def shell_quote(*inputs):
"""
Quote strings so that they can be suitably martialled
off to the shell. This method supports POSIX sh syntax.
This is crucial to properly handle command line arguments
with spaces, quotes, double-quotes, etc.
"""
regex = re.compile('[^\w!%+,\-./:@^]')
quote_regex = re.compile("((?:'\\''){2,})")
ret = []
for input in inputs:
if not input:
continue
if '\x00' in input:
raise AssertionError,('No way to quote strings '
'containing null(\\000) bytes')
# = does need quoting else in command position it's a
# program-local environment setting
match = regex.search(input)
if match and '=' not in input:
# ' -> '\''
input = input.replace("'", "'\\''")
# make multiple ' in a row look simpler
# '\'''\'''\'' -> '"'''"'
quote_match = quote_regex.match(input)
if quote_match:
quotes = match.group(1)
input.replace(quotes, ("'" *(len(quotes)/4)) + "\"'")
input = "'%s'" % input
if input.startswith("''"):
input = input[2:]
if input.endswith("''"):
input = input[:-2]
ret.append(input)
return ' '.join(ret)
HEADER_LENGTH = 80
def header(msg):
pad = HEADER_LENGTH - len(msg) - 4 # len(':+') + len('+:')
extra = pad % 2
pad /= 2
return(':+'
+(' ' * pad)
+ msg
+(' ' * (pad + extra))
+ '+:'
+ '\n')
def parse_geom(geomstr):
regex = re.compile('^(\d+)x(\d+)\+(\d+),(\d+)')
match = regex.match(geomstr)
if match:
defaults.WIDTH = int(match.group(1))
defaults.HEIGHT = int(match.group(2))
defaults.X = int(match.group(3))
defaults.Y = int(match.group(4))
return (defaults.WIDTH,
defaults.HEIGHT,
defaults.X,
defaults.Y)
def get_geom():
return '%dx%d+%d,%d' % (defaults.WIDTH,
defaults.HEIGHT,
defaults.X,
defaults.Y)
def project_name():
return os.path.basename(defaults.DIRECTORY)
def slurp(path):
file = open(path)
slushy = file.read()
file.close()
return slushy
def write(path, contents):
file = open(path, 'w')
file.write(contents)
file.close()
class DiffParser(object):
def __init__(self, model, filename='', cached=True, branch=None):
self.__header_re = re.compile('^@@ -(\d+),(\d+) \+(\d+),(\d+) @@.*')
self.__headers = []
self.__idx = -1
self.__diffs = []
self.__diff_spans = []
self.__diff_offsets = []
self.start = None
self.end = None
self.offset = None
self.diffs = []
self.selected = []
(header, diff) = model.diff_helper(filename=filename,
branch=branch,
with_diff_header=True,
cached=cached and not bool(branch),
reverse=cached or bool(branch))
self.model = model
self.diff = diff
self.header = header
self.parse_diff(diff)
# Always index into the non-reversed diff
self.fwd_header, self.fwd_diff = \
model.diff_helper(filename=filename,
branch=branch,
with_diff_header=True,
cached=cached and not bool(branch),
reverse=bool(branch))
def write_diff(self,filename,which,selected=False,noop=False):
if not noop and which < len(self.diffs):
diff = self.diffs[which]
write(filename, self.header + '\n' + diff + '\n')
return True
else:
return False
def get_diffs(self):
return self.__diffs
def get_diff_subset(self, diff, start, end):
adds = 0
deletes = 0
newdiff = []
local_offset = 0
offset = self.__diff_spans[diff][0]
diffguts = '\n'.join(self.__diffs[diff])
for line in self.__diffs[diff]:
line_start = offset + local_offset
local_offset += len(line) + 1 #\n
line_end = offset + local_offset
# |line1 |line2 |line3 |
# |--selection--|
# '-start '-end
# selection has head of diff (line3)
if start < line_start and end > line_start and end < line_end:
newdiff.append(line)
if line.startswith('+'):
adds += 1
if line.startswith('-'):
deletes += 1
# selection has all of diff (line2)
elif start <= line_start and end >= line_end:
newdiff.append(line)
if line.startswith('+'):
adds += 1
if line.startswith('-'):
deletes += 1
# selection has tail of diff (line1)
elif start >= line_start and start < line_end - 1:
newdiff.append(line)
if line.startswith('+'):
adds += 1
if line.startswith('-'):
deletes += 1
else:
# Don't add new lines unless selected
if line.startswith('+'):
continue
elif line.startswith('-'):
# Don't remove lines unless selected
newdiff.append(' ' + line[1:])
else:
newdiff.append(line)
new_count = self.__headers[diff][1] + adds - deletes
if new_count != self.__headers[diff][3]:
header = '@@ -%d,%d +%d,%d @@' % (
self.__headers[diff][0],
self.__headers[diff][1],
self.__headers[diff][2],
new_count)
newdiff[0] = header
return (self.header + '\n' + '\n'.join(newdiff) + '\n')
def get_spans(self):
return self.__diff_spans
def get_offsets(self):
return self.__diff_offsets
def set_diff_to_offset(self, offset):
self.offset = offset
self.diffs, self.selected = self.get_diff_for_offset(offset)
def set_diffs_to_range(self, start, end):
self.start = start
self.end = end
self.diffs, self.selected = self.get_diffs_for_range(start,end)
def get_diff_for_offset(self, offset):
for idx, diff_offset in enumerate(self.__diff_offsets):
if offset < diff_offset:
return (['\n'.join(self.__diffs[idx])], [idx])
return ([],[])
def get_diffs_for_range(self, start, end):
diffs = []
indices = []
for idx, span in enumerate(self.__diff_spans):
has_end_of_diff = start >= span[0] and start < span[1]
has_all_of_diff = start <= span[0] and end >= span[1]
has_head_of_diff = end >= span[0] and end <= span[1]
selected_diff =(has_end_of_diff
or has_all_of_diff
or has_head_of_diff)
if selected_diff:
diff = '\n'.join(self.__diffs[idx])
diffs.append(diff)
indices.append(idx)
return diffs, indices
def parse_diff(self, diff):
total_offset = 0
self.__idx = -1
self.__headers = []
for idx, line in enumerate(diff.splitlines()):
match = self.__header_re.match(line)
if match:
self.__headers.append([
int(match.group(1)),
int(match.group(2)),
int(match.group(3)),
int(match.group(4))
])
self.__diffs.append( [line] )
line_len = len(line) + 1 #\n
self.__diff_spans.append([total_offset,
total_offset + line_len])
total_offset += line_len
self.__diff_offsets.append(total_offset)
self.__idx += 1
else:
if self.__idx < 0:
errmsg = 'Malformed diff?\n\n%s' % diff
raise AssertionError, errmsg
line_len = len(line) + 1
total_offset += line_len
self.__diffs[self.__idx].append(line)
self.__diff_spans[-1][-1] += line_len
self.__diff_offsets[self.__idx] += line_len
def process_diff_selection(self, selected, offset, selection, branch=False):
if selection:
start = self.fwd_diff.index(selection)
end = start + len(selection)
self.set_diffs_to_range(start, end)
else:
self.set_diff_to_offset(offset)
selected = False
# Process diff selection only
if selected:
for idx in self.selected:
contents = self.get_diff_subset(idx, start, end)
if contents:
tmpfile = self.model.get_tmp_filename()
write(tmpfile, contents)
if branch:
self.model.apply_diff_to_worktree(tmpfile)
else:
self.model.apply_diff(tmpfile)
os.unlink(tmpfile)
# Process a complete hunk
else:
for idx, diff in enumerate(self.diffs):
tmpfile = self.model.get_tmp_filename()
if self.write_diff(tmpfile,idx):
if branch:
self.model.apply_diff_to_worktree(tmpfile)
else:
self.model.apply_diff(tmpfile)
os.unlink(tmpfile)
def sanitize_input(input):
for c in """ \t!@#$%^&*()\\;,<>"'[]{}~|""":
input = input.replace(c, '_')
return input
+206
View File
@@ -0,0 +1,206 @@
import os
import time
from PyQt4 import QtCore
from PyQt4.QtGui import qApp
from PyQt4.QtGui import QDialog
from PyQt4.QtGui import QMainWindow
from PyQt4.QtGui import QCheckBox
from PyQt4.QtGui import QSplitter
from cola import qtutils
from cola.syntax import DiffSyntaxHighlighter
from cola.syntax import LogSyntaxHighlighter
from main import Ui_main
from combo import Ui_combo
from items import Ui_items
from remote import Ui_remote
from commit import Ui_commit
from logger import Ui_logger
from search import Ui_search
from options import Ui_options
from createbranch import Ui_createbranch
from merge import Ui_merge
from bookmark import Ui_bookmark
from stash import Ui_stash
def CreateStandardView(uiclass, qtclass, *classes):
"""CreateStandardView returns a class closure of uiclass and qtclass.
This class performs the standard setup common to all view classes."""
class StandardView(uiclass, qtclass):
def __init__(self, parent=None, *args, **kwargs):
qtclass.__init__(self, parent)
uiclass.__init__(self)
self.parent_view = parent
self.setupUi(self)
self.init(parent, *args, **kwargs)
for cls in classes:
cls.init(self, parent, *args, **kwargs)
def init(self, parent, *args, **kwargs):
pass
return StandardView
class View(CreateStandardView(Ui_main, QMainWindow)):
"""The main cola interface."""
def init(self, parent=None):
self.staged.setAlternatingRowColors(True)
self.unstaged.setAlternatingRowColors(True)
self.set_display = self.display_text.setText
self.set_info = self.displayLabel.setText
self.action_undo = self.commitmsg.undo
self.action_redo = self.commitmsg.redo
self.action_paste = self.commitmsg.paste
self.action_select_all = self.commitmsg.selectAll
# Handle automatically setting the horizontal/vertical orientation
self.splitter.resizeEvent = self.splitter_resize_event
# Qt does not support noun/verbs
self.commit_button.setText(qtutils.tr('Commit@@verb'))
self.commit_menu.setTitle(qtutils.tr('Commit@@verb'))
# Default to creating a new commit(i.e. not an amend commit)
self.new_commit_radio.setChecked(True)
self.toolbar_show_log =\
self.toolbar.addAction(qtutils.get_qicon('git.png'),
'Show/Hide Log Window')
self.toolbar_show_log.setEnabled(True)
# Setup the default dock layout
self.tabifyDockWidget(self.diff_dock, self.editor_dock)
dock_area = QtCore.Qt.TopDockWidgetArea
self.addDockWidget(dock_area, self.status_dock)
toolbar_area = QtCore.Qt.BottomToolBarArea
self.addToolBar(toolbar_area, self.toolbar)
# Diff/patch syntax highlighter
DiffSyntaxHighlighter(self.display_text.document())
def splitter_resize_event(self, event):
width = self.splitter.width()
height = self.splitter.height()
if width > height:
self.splitter.setOrientation(QtCore.Qt.Horizontal)
else:
self.splitter.setOrientation(QtCore.Qt.Vertical)
QSplitter.resizeEvent(self.splitter, event)
def action_cut(self):
self.action_copy()
self.action_delete()
def action_copy(self):
cursor = self.commitmsg.textCursor()
selection = cursor.selection().toPlainText()
qtutils.set_clipboard(selection)
def action_delete(self):
self.commitmsg.textCursor().removeSelectedText()
def reset_display(self):
self.set_display('')
self.set_info('')
def copy_display(self):
cursor = self.display_text.textCursor()
selection = cursor.selection().toPlainText()
qtutils.set_clipboard(selection)
def diff_selection(self):
cursor = self.display_text.textCursor()
offset = cursor.position()
selection = cursor.selection().toPlainText()
num_selected_lines = selection.count('\n')
return offset, selection
def display(self, text):
self.set_display(text)
self.diff_dock.raise_()
class LogView(CreateStandardView(Ui_logger, QDialog)):
"""A simple dialog to display command logs."""
def init(self, parent=None, output=None):
self.setWindowTitle(self.tr('Git Command Log'))
LogSyntaxHighlighter(self.output_text.document())
if output:
self.set_output(output)
def clear(self):
self.output_text.clear()
def set_output(self, output):
self.output_text.setText(output)
def log(self, output):
if not output: return
cursor = self.output_text.textCursor()
cursor.movePosition(cursor.End)
text = self.output_text
cursor.insertText(time.asctime() + '\n')
for line in unicode(output).splitlines():
cursor.insertText(line + '\n')
cursor.insertText('\n')
cursor.movePosition(cursor.End)
text.setTextCursor(cursor)
class ItemView(object):
def init(self, parent, title="", items=[]):
self.setWindowTitle(title)
self.items = []
self.items.extend(items)
self.items_widget.addItems(items)
def idx(self):
return 0
def get_selected(self):
geom = qApp.desktop().screenGeometry()
width = geom.width()
height = geom.height()
x = self.parent_view.x() + self.parent_view.width()/2 - self.width()/2
y = self.parent_view.y() + self.parent_view.height()/3 - self.height()/2
self.move(x, y)
self.show()
if self.exec_() == QDialog.Accepted:
return self.items[self.idx()]
else:
return None
class ComboView(CreateStandardView(Ui_combo, QDialog, ItemView), ItemView):
"""A dialog for choosing branches."""
def idx(self):
return self.items_widget.currentIndex()
class ListView(CreateStandardView(Ui_items, QDialog, ItemView), ItemView):
"""A dialog for an item from a list."""
def idx(self):
return self.items_widget.currentRow()
class CommitView(CreateStandardView(Ui_commit, QDialog)):
def init(self, parent=None, title=None):
if title: self.setWindowTitle(title)
# Make the list widget slighty larger
self.splitter.setSizes([ 50, 200 ])
DiffSyntaxHighlighter(self.commit_text.document(),
whitespace=False)
class SearchView(CreateStandardView(Ui_search, QDialog)):
def init(self, parent=None):
self.input.setFocus()
DiffSyntaxHighlighter(self.commit_text.document(),
whitespace=False)
class MergeView(CreateStandardView(Ui_merge, QDialog)):
def init(self, parent=None):
self.revision.setFocus()
class RemoteView(CreateStandardView(Ui_remote, QDialog)):
def init(self, parent=None, button_text=''):
if button_text:
self.action_button.setText(button_text)
self.setWindowTitle(button_text)
def select_first_remote(self):
item = self.remotes.item(0)
if item:
self.remotes.setItemSelected(item, True)
self.remotes.setCurrentItem(item)
self.remotename.setText(item.text())
return True
else:
return False
# These are views that do not contain any custom methods
CreateBranchView = CreateStandardView(Ui_createbranch, QDialog)
OptionsView = CreateStandardView(Ui_options, QDialog)
BookmarkView = CreateStandardView(Ui_bookmark, QDialog)
StashView = CreateStandardView(Ui_stash, QDialog)
+6
View File
@@ -0,0 +1,6 @@
#!/usr/bin/env python
def build(bld):
# Compile the cola support modules
pyqt = bld.create_obj('py')
pyqt.inst_var = 'COLA_VIEWS'
pyqt.find_sources_in_dirs('.')
+13
View File
@@ -0,0 +1,13 @@
#!/usr/bin/env python
import Common
def build(bld):
# Compile the controllers
bld.add_subdirs('views controllers')
# Compile the cola support modules
pyqt = bld.create_obj('py')
pyqt.inst_var = 'COLA_MODULES'
pyqt.find_sources_in_dirs('.')
# Install the git-cola launcher
Common.install_files('COLA_BINDIR', '', 'git-cola', chmod=0755)
Common.symlink_as('COLA_BINDIR', 'git-cola', 'cola')
Vendored Executable
+146
View File
@@ -0,0 +1,146 @@
#! /bin/sh
# waf configure wrapper
# Fancy colors used to beautify the output a bit.
#
if [ $NOCOLOR ] ; then
NORMAL=""
BOLD=""
RED=""
YELLOW=""
GREEN=""
else
NORMAL="\033[0m"
BOLD="\033[1m"
RED="\033[91m"
YELLOW="\033[01;93m"
GREEN="\033[92m"
fi
EXIT_SUCCESS=0
EXIT_FAILURE=1
EXIT_ERROR=2
EXIT_BUG=10
CUR_DIR=$PWD
#possible relative path
WORKINGDIR=$(dirname $0)
cd $WORKINGDIR
#abs path
WORKINGDIR=$(pwd)
TESTDIR="$WORKINGDIR/t"
cd $CUR_DIR
# We store waf under build/
PATH=$WORKINGDIR/build:$PATH
export PATH
# Checks for Python interpreter. Honours $PYTHON if set. Stores path to
# interpreter in $PYTHON.
#
checkPython()
{
if [ -z "$PYTHON" ] ; then
PYTHON=$(which python 2>/dev/null)
fi
printf "Checking for Python\t\t\t: "
if [ ! -x "$PYTHON" ] ; then
printf $RED"not found!"$NORMAL"\n"
echo "Please make sure that the Python interpreter is available in your PATH"
echo "or invoke configure using the PYTHON flag, e.g."
echo "$ PYTHON=/usr/local/bin/python configure"
exit $EXIT_FAILURE
fi
printf $GREEN"$PYTHON"$NORMAL"\n"
}
# Checks for WAF. Honours $WAF if set. Stores path to 'waf' in $WAF.
# Requires that $PYTHON is set.
#
checkWAF()
{
#installed miniwaf in sourcedir
if [ -z "$WAF" ] ; then
if [ -f "${WORKINGDIR}/build/waf" ] ; then
WAF="${WORKINGDIR}/build/waf"
if [ ! -x $WAF ] ; then
chmod +x $WAF
fi
fi
fi
#global installed waf with waf->waf.py link
if [ -z $WAF ] ; then
WAF=$(which waf 2>/dev/null)
fi
# neither waf nor miniwaf could be found
if [ ! -x "$WAF" ] ; then
printf $RED"not found"$NORMAL"\n"
echo "Goto http://www.freehackers.org/~tnagy/bksys.html"
echo "and download a waf version"
exit $EXIT_FAILURE
else
printf $GREEN"$WAF"$NORMAL"\n"
fi
}
# Generates a Makefile. Requires that $WAF is set.
#
generateMakefile()
{
cat > Makefile << EOF
#!/usr/bin/make -f
# Waf Makefile wrapper
# http://www.freehackers.org/~tnagy/waf.html
WAF_HOME=$CUR_DIR
PYTHON=$PYTHON
all:
@\$(PYTHON) $WAF build
all_debug:
@\$(PYTHON) $WAF -v build
all_progress:
@\$(PYTHON) $WAF -p build
install:
@\$(PYTHON) $WAF install
uninstall:
@\$(PYTHON) $WAF uninstall
clean:
@\$(PYTHON) $WAF clean
distclean:
@\$(PYTHON) $WAF distclean
@-rm -rf cache/
@-rm -rf _build_
@-rm -f Makefile
check:
@\$(PYTHON) $WAF check
dist:
@\$(PYTHON) $WAF dist
test:
@$TESTDIR/run_tests
EOF
}
checkPython
checkWAF
echo "calling waf configure with parameters"
$WAF configure $* || exit $EXIT_ERROR
#create a Makefile if waf configure succeeds
if [ -f "${WORKINGDIR}/.lock-wscript" ] ; then
generateMakefile
fi
exit $EXIT_SUCCESS
+5
View File
@@ -0,0 +1,5 @@
cola (1.0.0-1) unstable; urgency=low
Initial debian version
-- David Aguilar <davvid@gmail.com> Tue, 10 Jun 2008 12:00:00 -0700
+1
View File
@@ -0,0 +1 @@
5
+11
View File
@@ -0,0 +1,11 @@
Source: cola
Section: devel
Priority: extra
Maintainer: David Aguilar <davvid@gmail.com>
Build-Depends: debhelper (>= 5), git-core (>= 1.5), python-all-dev, python-git (>= 0.3), gettext, pyqt4-dev-tools
Standards-Version: 3.7.2
Package: cola
Architecture: all
Depends: python (>= 2.4), python-qt4 (>= 4.3), git-core (>= 1.5), python-git (>= 0.3)
Description: a highly caffeinated git gui
+34
View File
@@ -0,0 +1,34 @@
This package was debianized by David Aguilar <davvid@gmail.com> on
Sun, 13 Apr 2008 18:28:41 -0700.
It was downloaded from http://gitcola.tuxfamily.org/releases
Upstream Author(s):
David Aguilar <davvid@gmail.com>
Copyright:
Copyright (C) 2008 David Aguilar
License:
This package is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This package is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this package; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
On Debian systems, the complete text of the GNU General
Public License can be found in `/usr/share/common-licenses/LGPL'.
The Debian packaging is (C) 2008, David Aguilar <davvid@gmail.com> and
is licensed under the GPL, see above.
Vendored
+5
View File
@@ -0,0 +1,5 @@
usr/bin
usr/share/applications
usr/share/cola
usr/share/cola/controllers
usr/share/cola/views
Vendored
View File
Vendored Executable
+49
View File
@@ -0,0 +1,49 @@
#!/usr/bin/make -f
# -*- makefile -*-
# Sample debian/rules that uses debhelper.
# This file was originally written by Joey Hess and Craig Small.
# As a special exception, when this file is copied by dh-make into a
# dh-make output file, you may use that output file without restriction.
# This special exception was added by Craig Small in version 0.37 of dh-make.
# Uncomment this to turn on verbose mode.
#export DH_VERBOSE=1
DEBIAN=$(CURDIR)/debian
BUILD=$(DEBIAN)/bld
configure: $(BUILD)/configure-stamp
$(BUILD)/configure-stamp:
./configure \
--prefix=/usr \
--blddir=$(BUILD) \
--destdir=$(DEBIAN)/cola
touch $(BUILD)/configure-stamp
build: $(BUILD)/build-stamp
$(BUILD)/build-stamp: $(BUILD)/configure-stamp
$(MAKE)
touch $(BUILD)/build-stamp
clean:
dh_testdir
dh_testroot
dh_clean
rm -rf $(BUILD)
install: build
# Build architecture-independent files here.
binary-indep: build install
dh_testdir
dh_testroot
dh_clean -k
dh_installdirs
$(MAKE) DESTDIR=$(DEBIAN)/cola install
dh_fixperms
dh_installdeb
dh_gencontrol
dh_md5sums
dh_builddeb
binary: binary-indep
.PHONY: build clean binary-indep binary install configure
+177
View File
@@ -0,0 +1,177 @@
The following license applies to all icons except staged.png.
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
+5
View File
@@ -0,0 +1,5 @@
ACKNOWLEDGEMENTS:
staged.png is from the "slick" KDE icon set.
All other icons are from the Apache httpd server icon set.
See the COPYING file for more details.
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 301 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 202 B

+2
View File
@@ -0,0 +1,2 @@
*.msg
*~
+4
View File
@@ -0,0 +1,4 @@
These files are from the git-gui sources.
Please see git-gui's po/README file for more details.
These files are distributes under the same license as git-gui.
+1904
View File
File diff suppressed because it is too large Load Diff
+185
View File
@@ -0,0 +1,185 @@
# Translation of git-gui glossary to German
# Copyright (C) 2007 Shawn Pearce, et al.
# This file is distributed under the same license as the git package.
# Christian Stimming <stimming@tuhh.de>, 2007
#
msgid ""
msgstr ""
"Project-Id-Version: git-gui glossary\n"
"POT-Creation-Date: 2007-10-19 21:43+0200\n"
"PO-Revision-Date: 2007-10-20 15:24+0200\n"
"Last-Translator: Christian Stimming <stimming@tuhh.de>\n"
"Language-Team: German \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#. "English Definition (Dear translator: This file will never be visible to the user! It should only serve as a tool for you, the translator. Nothing more.)"
msgid ""
"English Term (Dear translator: This file will never be visible to the user!)"
msgstr ""
"Deutsche Übersetzung.\n"
"Andere deutsche SCM:\n"
" http://tortoisesvn.net/docs/release/TortoiseSVN_de/index.html und http://"
"tortoisesvn.tigris.org/svn/tortoisesvn/trunk/Languages/Tortoise_de.po "
"(username=guest, password empty, gut),\n"
" http://msdn.microsoft.com/de-de/library/ms181038(vs.80).aspx (MS Visual "
"Source Safe, kommerziell),\n"
" http://cvsbook.red-bean.com/translations/german/Kap_06.html "
"(mittelmäßig),\n"
" http://tortoisecvs.cvs.sourceforge.net/tortoisecvs/po/TortoiseCVS/de_DE.po?"
"view=markup (mittelmäßig),\n"
" http://rapidsvn.tigris.org/svn/rapidsvn/trunk/src/locale/de/rapidsvn.po "
"(username=guest, password empty, schlecht)"
#. ""
msgid "amend"
msgstr "nachbessern (ergänzen)"
#. ""
msgid "annotate"
msgstr "annotieren"
#. "A 'branch' is an active line of development."
msgid "branch [noun]"
msgstr "Zweig"
#. ""
msgid "branch [verb]"
msgstr "verzweigen"
#. ""
msgid "checkout [noun]"
msgstr ""
"Arbeitskopie (Erstellung einer Arbeitskopie; Auscheck? Ausspielung? Abruf? "
"Source Safe: Auscheckvorgang)"
#. "The action of updating the working tree to a revision which was stored in the object database."
msgid "checkout [verb]"
msgstr ""
"Arbeitskopie erstellen; Zweig umstellen [checkout a branch] (auschecken? "
"ausspielen? abrufen? Source Safe: auschecken)"
#. ""
msgid "clone [verb]"
msgstr "kopieren"
#. "A single point in the git history."
msgid "commit [noun]"
msgstr ""
"Version; Eintragung; Änderung (Buchung?, Eintragung?, Übertragung?, "
"Sendung?, Übergabe?, Einspielung?, Ablagevorgang?)"
#. "The action of storing a new snapshot of the project's state in the git history."
msgid "commit [verb]"
msgstr ""
"eintragen (TortoiseSVN: übertragen; Source Safe: einchecken; senden?, "
"übergeben?, einspielen?, einpflegen?, ablegen?)"
#. ""
msgid "diff [noun]"
msgstr "Vergleich (Source Safe: Unterschiede)"
#. ""
msgid "diff [verb]"
msgstr "vergleichen"
#. "A fast-forward is a special type of merge where you have a revision and you are merging another branch's changes that happen to be a descendant of what you have."
msgid "fast forward merge"
msgstr "Schnellzusammenführung"
#. "Fetching a branch means to get the branch's head from a remote repository, to find out which objects are missing from the local object database, and to get them, too."
msgid "fetch"
msgstr "anfordern (holen?)"
#. "A collection of files. The index is a stored version of your working tree."
msgid "index (in git-gui: staging area)"
msgstr "Bereitstellung"
#. "A successful merge results in the creation of a new commit representing the result of the merge."
msgid "merge [noun]"
msgstr "Zusammenführung"
#. "To bring the contents of another branch into the current branch."
msgid "merge [verb]"
msgstr "zusammenführen"
#. ""
msgid "message"
msgstr "Beschreibung (Meldung?, Nachricht?; Source Safe: Kommentar)"
#. "Deletes all stale tracking branches under <name>. These stale branches have already been removed from the remote repository referenced by <name>, but are still locally available in 'remotes/<name>'."
msgid "prune"
msgstr "entfernen"
#. "Pulling a branch means to fetch it and merge it."
msgid "pull"
msgstr "übernehmen (ziehen?)"
#. "Pushing a branch means to get the branch's head ref from a remote repository, and ... (well, can someone please explain it for mere mortals?)"
msgid "push"
msgstr "versenden (ausliefern? hochladen? verschicken? schieben?)"
#. ""
msgid "redo"
msgstr "wiederholen"
#. "An other repository ('remote'). One might have a set of remotes whose branches one tracks."
msgid "remote"
msgstr "Andere Archive (Gegenseite?, Entfernte?, Server?)"
#. "A collection of refs (?) together with an object database containing all objects which are reachable from the refs... (oops, you've lost me here. Again, please an explanation for mere mortals?)"
msgid "repository"
msgstr "Projektarchiv"
#. ""
msgid "reset"
msgstr "zurücksetzen (zurückkehren?)"
#. ""
msgid "revert"
msgstr "revidieren"
#. "A particular state of files and directories which was stored in the object database."
msgid "revision"
msgstr "Version (TortoiseSVN: Revision; Source Safe: Version)"
#. ""
msgid "sign off"
msgstr "abzeichnen (gegenzeichnen?, freizeichnen?, absegnen?)"
#. ""
msgid "staging area"
msgstr "Bereitstellung"
#. ""
msgid "status"
msgstr "Status"
#. "A ref pointing to a tag or commit object"
msgid "tag [noun]"
msgstr "Markierung"
#. ""
msgid "tag [verb]"
msgstr "markieren"
#. "A regular git branch that is used to follow changes from another repository."
msgid "tracking branch"
msgstr "Übernahmezweig"
#. ""
msgid "undo"
msgstr "rückgängig"
#. ""
msgid "update"
msgstr "aktualisieren"
#. ""
msgid "verify"
msgstr "überprüfen"
#. "The tree of actual checked out files."
msgid "working copy, working tree"
msgstr "Arbeitskopie"
+184
View File
@@ -0,0 +1,184 @@
# Translation of git-gui glossary to Italian
# Copyright (C) 2007 Shawn Pearce, et al.
# This file is distributed under the same license as the git package.
# Christian Stimming <stimming@tuhh.de>, 2007
#
msgid ""
msgstr ""
"Project-Id-Version: git-gui glossary\n"
"POT-Creation-Date: 2007-10-19 21:43+0200\n"
"PO-Revision-Date: 2007-10-10 15:24+0200\n"
"Last-Translator: Michele Ballabio <barra_cuda@katamail.com>\n"
"Language-Team: Italian \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#. "English Definition (Dear translator: This file will never be visible to the user! It should only serve as a tool for you, the translator. Nothing more.)"
msgid ""
"English Term (Dear translator: This file will never be visible to the user!)"
msgstr ""
"Traduzione italiana.\n"
"Altri SCM in italiano:\n"
" http://tortoisesvn.tigris.org/svn/tortoisesvn/trunk/Languages/Tortoise_it."
"po (username=guest, password empty),\n"
" http://tortoisecvs.cvs.sourceforge.net/tortoisecvs/po/TortoiseCVS/it_IT.po?"
"view=markup ,\n"
" http://rapidsvn.tigris.org/svn/rapidsvn/trunk/src/locale/it_IT/rapidsvn.po "
"(username=guest, password empty)"
#. ""
msgid "amend"
msgstr "correggere, correzione"
#. ""
msgid "annotate"
msgstr "annotare, annotazione"
#. "A 'branch' is an active line of development."
msgid "branch [noun]"
msgstr "ramo, diramazione, ramificazione"
#. ""
msgid "branch [verb]"
msgstr "creare ramo, ramificare, diramare"
#. ""
msgid "checkout [noun]"
msgstr "attivazione, checkout, revisione attiva, prelievo (TortoiseCVS)?"
#. "The action of updating the working tree to a revision which was stored in the object database."
msgid "checkout [verb]"
msgstr ""
"attivare, effettuare un checkout, attivare revisione, prelevare "
"(TortoiseCVS), ritirare (TSVN)?"
#. ""
msgid "clone [verb]"
msgstr "clonare"
#. "A single point in the git history."
msgid "commit [noun]"
msgstr "revisione, commit, deposito (TortoiseCVS), invio (TSVN)?"
#. "The action of storing a new snapshot of the project's state in the git history."
msgid "commit [verb]"
msgstr ""
"creare una nuova revisione, archiviare, effettuare un commit, depositare "
"(nel server), fare un deposito (TortoiseCVS), inviare (TSVN)?"
#. ""
msgid "diff [noun]"
msgstr "differenza, confronto, comparazione, raffronto"
#. ""
msgid "diff [verb]"
msgstr "confronta, mostra le differenze"
#. "A fast-forward is a special type of merge where you have a revision and you are merging another branch's changes that happen to be a descendant of what you have."
msgid "fast forward merge"
msgstr "fusione in 'fast-forward', fusione in avanti veloce"
#. "Fetching a branch means to get the branch's head from a remote repository, to find out which objects are missing from the local object database, and to get them, too."
msgid "fetch"
msgstr "recuperare, prelevare, prendere da, recuperare (TSVN)"
#. "A collection of files. The index is a stored version of your working tree."
msgid "index (in git-gui: staging area)"
msgstr "indice"
#. "A successful merge results in the creation of a new commit representing the result of the merge."
msgid "merge [noun]"
msgstr "fusione, unione"
#. "To bring the contents of another branch into the current branch."
msgid "merge [verb]"
msgstr "effettuare la fusione, unire, fondere, eseguire la fusione"
#. ""
msgid "message"
msgstr "messaggio, commento"
#. "Deletes all stale tracking branches under <name>. These stale branches have already been removed from the remote repository referenced by <name>, but are still locally available in 'remotes/<name>'."
msgid "prune"
msgstr "potatura"
#. "Pulling a branch means to fetch it and merge it."
msgid "pull"
msgstr ""
"prendi (recupera) e fondi (unisci)? (in pratica una traduzione di fetch + "
"merge)"
#. "Pushing a branch means to get the branch's head ref from a remote repository, and ... (well, can someone please explain it for mere mortals?)"
msgid "push"
msgstr "propaga"
#. ""
msgid "redo"
msgstr "ripeti, rifai"
#. "An other repository ('remote'). One might have a set of remotes whose branches one tracks."
msgid "remote"
msgstr "remoto"
#. "A collection of refs (?) together with an object database containing all objects which are reachable from the refs... (oops, you've lost me here. Again, please an explanation for mere mortals?)"
msgid "repository"
msgstr "archivio, repository, database? deposito (rapidsvn)?"
#. ""
msgid "reset"
msgstr "ripristinare, annullare, azzerare, ripristinare"
#. ""
msgid "revert"
msgstr ""
"annullare, inverti (rapidsvn), ritorna allo stato precedente, annulla le "
"modifiche della revisione"
#. "A particular state of files and directories which was stored in the object database."
msgid "revision"
msgstr "revisione (TortoiseSVN)"
#. ""
msgid "sign off"
msgstr "sign off, firma"
#. ""
msgid "staging area"
msgstr ""
"area di preparazione, zona di preparazione, modifiche in preparazione? "
"modifiche in allestimento?"
#. ""
msgid "status"
msgstr "stato"
#. "A ref pointing to a tag or commit object"
msgid "tag [noun]"
msgstr "etichetta, etichettatura (TortoiseCVS)"
#. ""
msgid "tag [verb]"
msgstr "etichettare"
#. "A regular git branch that is used to follow changes from another repository."
msgid "tracking branch"
msgstr ""
"duplicato locale di ramo remoto, ramo in 'tracking', ramo inseguitore? ramo "
"di {inseguimento,allineamento,rilevamento,puntamento}?"
#. ""
msgid "undo"
msgstr "annulla"
#. ""
msgid "update"
msgstr "aggiornamento, aggiornare"
#. ""
msgid "verify"
msgstr "verifica, verificare"
#. "The tree of actual checked out files."
msgid "working copy, working tree"
msgstr "directory di lavoro, copia di lavoro"
+170
View File
@@ -0,0 +1,170 @@
# Translation of git-gui glossary to Simplified Chinese
# Copyright (C) 2007 Shawn Pearce, et al.
# This file is distributed under the same license as the git package.
# Xudong Guan <xudong.guan@gmail.com> and the zh-kernel.org mailing list, 2007
#
msgid ""
msgstr ""
"Project-Id-Version: git-gui glossary\n"
"PO-Revision-Date: 2007-07-23 22:07+0200\n"
"Last-Translator: Xudong Guan <xudong.guan@gmail.com>\n"
"Language-Team: Simplified Chinese \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#. "English Definition (Dear translator: This file will never be visible to the user! It should only serve as a tool for you, the translator. Nothing more.)"
msgid ""
"English Term (Dear translator: This file will never be visible to the user!)"
msgstr "注:这个文件是为了帮助翻译人员统一名词术语。最终用户不会关心这个文件。"
#. ""
#. amend指用户修改最近一次commit的操作,修订?修改?修正?
#. [WANG Cong]: 根据我的了解,这个词似乎翻译成“修订”多一些。“修正”也可以,“修改”再次之。
#. [ZHANG Le]: 修订,感觉一般指对一些大型出版物的大规模升级,比如修订新华字典
# 修正,其实每次amend的结果也不一定就是最后结果,说不定还需要修改。所以不
# 如就叫修改
msgid "amend"
msgstr "修订"
#. ""
#. git annotate 文件名:用来标注文件的每一行在什么时候被谁最后修改。
#. [WANG Cong]: "标记"一般是mark。;)
#. [ZHANG Le]: 标注,或者干脆用原意:注解,或注释
msgid "annotate"
msgstr "标注"
#. "A 'branch' is an active line of development."
msgid "branch [noun]"
msgstr "分支"
#. ""
msgid "branch [verb]"
msgstr "建立分支"
#. ""
#. [WANG Cong]: 网上有人翻译成“检出”,我感觉更好一些,毕竟把check的意思翻译出来了。
#. [ZHNAG Le]: 提取吧,提取分支/版本
#. [rae l]: 签出。subversion软件中的大多词汇已有翻译,既然git与subversion同是SCM管理,可以参考同类软件的翻译也不错。
msgid "checkout [noun]"
msgstr "签出"
#. "The action of updating the working tree to a revision which was stored in the object database."
msgid "checkout [verb]"
msgstr "签出"
#. "A single point in the git history."
msgid "commit [noun]"
msgstr "提交"
#. "The action of storing a new snapshot of the project's state in the git history."
msgid "commit [verb]"
msgstr "提交"
#. ""
#. 差异?差别?
#. [ZHANG Le]: 个人感觉差别更加中性一些
msgid "diff [noun]"
msgstr "差别"
#. ""
msgid "diff [verb]"
msgstr "比较"
#. "A fast-forward is a special type of merge where you have a revision and you are merging another branch's changes that happen to be a descendant of what you have."
msgid "fast forward merge"
msgstr "快进式合并"
#. "Fetching a branch means to get the branch's head from a remote repository, to find out which objects are missing from the local object database, and to get them, too."
#. 获取?取得?下载?更新?注意和update的区分
msgid "fetch"
msgstr "获取"
#. "A collection of files. The index is a stored version of your working tree."
#. index是working tree和repository之间的缓存
msgid "index (in git-gui: staging area)"
msgstr "工作缓存?"
#. "A successful merge results in the creation of a new commit representing the result of the merge."
msgid "merge [noun]"
msgstr "合并"
#. "To bring the contents of another branch into the current branch."
msgid "merge [verb]"
msgstr "合并"
#. ""
#. message是指commit中的文字信息
msgid "message"
msgstr "描述"
#. "Pulling a branch means to fetch it and merge it."
msgid "pull"
msgstr "获取+合并"
#. "Pushing a branch means to get the branch's head ref from a remote repository, and ... (well, can someone please explain it for mere mortals?)"
msgid "push"
msgstr "推入"
#. ""
msgid "redo"
msgstr "重做"
#. "A collection of refs (?) together with an object database containing all objects which are reachable from the refs... (oops, you've lost me here. Again, please an explanation for mere mortals?)"
msgid "repository"
msgstr "仓库"
#. ""
msgid "reset"
msgstr "重置"
#. ""
msgid "revert"
msgstr "恢复"
#. "A particular state of files and directories which was stored in the object database."
msgid "revision"
msgstr "版本"
#. ""
msgid "sign off"
msgstr "签名"
#. ""
#. 似乎是git-gui里面显示的本次提交的文件清单区域
msgid "staging area"
msgstr "提交暂存区"
#. ""
msgid "status"
msgstr "状态"
#. "A ref pointing to a tag or commit object"
msgid "tag [noun]"
msgstr "标签"
#. ""
msgid "tag [verb]"
msgstr "添加标签"
#. "A regular git branch that is used to follow changes from another repository."
msgid "tracking branch"
msgstr "跟踪分支"
#. ""
msgid "undo"
msgstr "撤销"
#. ""
msgid "update"
msgstr "更新。注意和fetch的区分"
#. ""
msgid "verify"
msgstr "验证"
#. "The tree of actual checked out files."
#. "工作副本?工作区域?工作目录"
#. [LI Yang]: 当前副本, 当前源码树?
msgid "working copy, working tree"
msgstr "工作副本,工作源码树"
+1937
View File
File diff suppressed because it is too large Load Diff
+1913
View File
File diff suppressed because it is too large Load Diff
+1883
View File
File diff suppressed because it is too large Load Diff
+1893
View File
File diff suppressed because it is too large Load Diff
+1769
View File
File diff suppressed because it is too large Load Diff
-252
View File
@@ -1,252 +0,0 @@
import os
import re
import time
import commands
import ugitutils
def git_add (to_add):
'''Invokes 'git add' to index the filenames in to_add.'''
if not to_add: return 'ERROR: No files to add.'
argv = [ 'git', 'add' ]
for filename in to_add:
argv.append (ugitutils.shell_quote (filename))
cmd = ' '.join (argv)
return 'Running:\t%s\n%s\n%s added successfully' % (
cmd, commands.getoutput (cmd), ', '.join (to_add) )
def git_add_or_remove (to_process):
'''Invokes 'git add' to index the filenames in to_process that exist
and 'git rm' for those that do not exist.'''
if not to_process: return 'ERROR: No files to add or remove.'
to_add = []
output = ''
for filename in to_process:
if os.path.exists (filename):
to_add.append (filename)
if to_add:
output += git_add (to_add) + '\n\n'
if len(to_add) == len(to_process):
# to_process only contained unremoved files --
# short-circuit the removal checks
return output
# Process files to add
argv = [ 'git', 'rm' ]
for filename in to_process:
if not os.path.exists (filename):
argv.append (ugitutils.shell_quote (filename))
cmd = ' '.join (argv)
return output + 'Running: %s\n%s' % ( cmd, commands.getoutput (cmd) )
def git_branch():
'''Returns 'git branch''s output in a list.'''
return commands.getoutput ('git branch').split ('\n')
def git_cherry_pick (revs, commit=False):
'''Cherry-picks each revision into the current branch.'''
if not revs:
return 'ERROR: No revisions selected for cherry-picking.'''
cmd = 'git cherry-pick '
if not commit: cmd += '-n '
output = []
for rev in revs:
output.append ('Cherry-picking: ' + rev)
output.append (commands.getoutput (cmd + rev))
output.append ('')
return '\n'.join (output)
def git_commit (msg, amend, commit_all, files):
'''Creates a git commit. 'commit_all' triggers the -a
flag to 'git commit.' 'amend' triggers --amend.
'files' is a list of files to use for commits without -a.'''
if not msg:
return 'ERROR: No commit message was provided.'
# Allow TMPDIR/TMP with a fallback to /tmp
tmpdir = os.getenv ('TMPDIR', os.getenv ('TMP', '/tmp'))
# Sure, this is a potential "security risk," but if someone
# is trying to intercept/re-write commit messages on your system,
# then you probably have bigger problems to worry about.
tmpfile = os.path.join (tmpdir,
'ugit.%s.%s' % ( os.getuid(), time.time() ))
argv = [ 'git', 'commit', '-F', tmpfile ]
if amend: argv.append ('--amend')
if commit_all:
argv.append ('-a')
else:
if not files:
return 'ERROR: No files selected for commit.'
argv.append ('--')
for file in files:
argv.append (ugitutils.shell_quote (file))
# Create the commit message file
file = open (tmpfile, 'w')
file.write (msg)
file.close()
# Run 'git commit'
cmd = ' '.join (argv)
output = commands.getoutput (cmd)
os.unlink (tmpfile)
return 'Running:\t%s\n%s' % ( cmd, output )
def git_current_branch():
'''Parses 'git branch' to find the current branch.'''
for branch in git_branch():
if branch.startswith ('* '):
return branch.lstrip ('* ')
raise Exception, 'No current branch. Detached HEAD?'
def git_diff (filename, staged=True):
'''Invokes git_diff on filename. Passing staged=True adds
diffs the index against HEAD (i.e. --cached).'''
deleted = False
argv = [ 'git', 'diff', '--color']
if staged:
deleted = not os.path.exists (filename)
argv.append ('--cached')
argv.append ('--')
argv.append (ugitutils.shell_quote (filename))
diff = commands.getoutput (' '.join (argv))
diff_lines = diff.split ('\n')
output = []
start = False
del_tag = 'deleted file mode '
for line in diff_lines:
if not start and '@@ ' in line and ' @@' in line:
start = True
if start or (deleted and del_tag in line):
output.append (line)
return '\n'.join (output)
def git_diff_stat ():
'''Returns the latest diffstat.'''
return commands.getoutput ('git diff --color --stat HEAD^')
def git_format_patch (revs, use_range):
'''Exports patches revs in the 'ugit-patches' subdirectory.
If use_range is True, a commit range is passed to git format-patch.'''
cmd = 'git format-patch --thread --patch-with-stat -o ugit-patches '
header = 'Generated Patches:'
if len (revs) > 1:
cmd += '-n '
if use_range:
rev_range = '%s^..%s' % ( revs[-1], revs[0] )
return header + '\n' + commands.getoutput (cmd + rev_range)
output = [ header ]
num_patches = 1
for idx, rev in enumerate (revs):
real_idx = idx + num_patches
revcmd = cmd + '-1 --start-number %d %s' % (real_idx, rev)
output.append (commands.getoutput (revcmd))
num_patches += output[-1].count ('\n')
return '\n'.join (output)
def git_log (oneline=True, all=False):
'''Returns a pair of parallel arrays listing the revision sha1's
and commit summaries.'''
argv = [ 'git', 'log' ]
if oneline: argv.append ('--pretty=oneline')
if all: argv.append ('--all')
revs = []
summaries = []
regex = re.compile ('(\w+)\W(.*)')
output = commands.getoutput (' '.join (argv))
for line in output.split ('\n'):
match = regex.match (line)
if match:
revs.append (match.group (1))
summaries.append (match.group (2))
return ( revs, summaries )
def git_reset (to_unstage):
'''Use 'git reset' to unstage files from the index.'''
if not to_unstage: return 'ERROR: No files to reset.'
argv = [ 'git', 'reset', '--' ]
for filename in to_unstage:
argv.append (ugitutils.shell_quote (filename))
cmd = ' '.join (argv)
return 'Running:\t%s\n%s' % ( cmd, commands.getoutput (cmd) )
def git_show (sha1, color=False):
cmd = 'git show '
if color: cmd += '--color '
return commands.getoutput (cmd + sha1)
def git_show_cdup():
'''Returns a relative path to the git project root.'''
return commands.getoutput ('git rev-parse --show-cdup')
def git_status():
'''RETURNS: A tuple of staged, unstaged and untracked files.
( array(staged), array(unstaged), array(untracked) )'''
status_lines = commands.getoutput ('git status').split ('\n')
unstaged_header_seen = False
untracked_header_seen = False
modified_header = '# Changed but not updated:'
modified_regex = re.compile('(#\tmodified:|#\tnew file:|#\tdeleted:)')
untracked_header = '# Untracked files:'
untracked_regex = re.compile ('#\t(.+)')
staged = []
unstaged = []
untracked = []
for status_line in status_lines:
if untracked_header in status_line:
untracked_header_seen = True
continue
if not untracked_header_seen:
continue
match = untracked_regex.match (status_line)
if match:
filename = match.group (1)
untracked.append (filename)
for status_line in status_lines:
if modified_header in status_line:
unstaged_header_seen = True
continue
match = modified_regex.match (status_line)
if match:
tag = match.group (0)
filename = status_line.replace (tag, '')
if unstaged_header_seen:
unstaged.append (filename.lstrip())
else:
staged.append (filename.lstrip())
return ( staged, unstaged, untracked )
-515
View File
@@ -1,515 +0,0 @@
import os
import commands
from PyQt4 import QtGui
from PyQt4.QtCore import QObject
from PyQt4.QtCore import SIGNAL
from PyQt4.QtGui import QClipboard
from PyQt4.QtGui import QDialog
from PyQt4.QtGui import QPixmap
from PyQt4.QtGui import QIcon
from PyQt4.QtGui import QListWidgetItem
from qobserver import QObserver
import ugitcmds
import ugitutils
from ugitview import GitCommandDialog
from ugitview import GitCommitBrowser
class GitController (QObserver):
'''The controller is a mediator between the model and view.
It allows for a clean decoupling between view and model classes.'''
def __init__ (self, model, view):
QObserver.__init__ (self, model, view)
# Register ourselves with the model
model.add_observer (self)
# Binds a specific model attribute to a view widget,
# and vice versa.
self.model_to_view (model, 'commitmsg', 'commitText')
# When a model attribute changes, this runs a specific action
self.add_actions (model, 'staged', self.action_staged)
self.add_actions (model, 'unstaged', self.action_unstaged)
self.add_actions (model, 'untracked', self.action_unstaged)
# Routes signals for multiple widgets to our callbacks
# defined below.
self.add_signals ('textChanged()', view.commitText)
self.add_signals ('stateChanged(int)', view.untrackedCheckBox)
self.add_signals ('pressed()',
view.rescanButton,
view.stageButton,
view.commitButton,
view.pushButton,
view.signOffButton,)
self.add_signals ('triggered()',
view.rescan,
view.commitAll,
view.commitSelected,
view.getCommitMessage,
view.stageChanged,
view.stageUntracked,
view.stageSelected,
view.unstageAll,
view.unstageSelected,
view.showDiffstat,
view.browseBranch,
view.browseOtherBranch,
view.visualizeAll,
view.visualizeCurrent,
view.exportPatches,
view.cherryPick,)
self.add_signals ('itemSelectionChanged()',
view.stagedList,
view.unstagedList,)
# Handle double-clicks in the staged/unstaged lists.
# These are vanilla signal/slots since the qobserver
# signal routing is already handling these lists' signals.
QObject.connect ( view.unstagedList,
SIGNAL ('itemDoubleClicked(QListWidgetItem*)'),
lambda (x): self.cb_stage_selected (model) )
QObject.connect ( view.stagedList,
SIGNAL ('itemDoubleClicked(QListWidgetItem*)'),
lambda (x): self.cb_unstage_selected (model) )
# These callbacks are called in response to the signals
# defined above. One property of the QObserver callback
# mechanism is that the model is passed in as the first
# argument to the callback. This allows for a single
# controller to manage multiple models, though this
# isn't used at the moment.
self.add_callbacks (model, {
# Push Buttons
'rescanButton': self.cb_rescan,
'stageButton': self.cb_stage_selected,
'signOffButton': self.cb_signoff,
'commitButton': self.cb_commit,
# Checkboxes
'untrackedCheckBox': self.cb_rescan,
# List Widgets
'stagedList': self.cb_diff_staged,
'unstagedList': self.cb_diff_unstaged,
# Menu Actions
'rescan': self.cb_rescan,
'commitAll': self.cb_commit_all,
'commitSelected': self.cb_commit_selected,
'getCommitMessage': self.cb_get_commit_msg,
'stageChanged': self.cb_stage_changed,
'stageUntracked': self.cb_stage_untracked,
'stageSelected': self.cb_stage_selected,
'unstageAll': self.cb_unstage_all,
'unstageSelected': self.cb_unstage_selected,
'showDiffstat': self.cb_show_diffstat,
'browseBranch': self.cb_browse_current,
'browseOtherBranch': self.cb_browse_other,
'visualizeCurrent': self.cb_viz_current,
'visualizeAll': self.cb_viz_all,
'exportPatches': self.cb_export_patches,
'cherryPick': self.cb_cherry_pick,
})
# chdir to the root of the git tree. This is critical
# to being able to properly use the git porcelain.
cdup = ugitcmds.git_show_cdup()
if cdup: os.chdir (cdup)
# Default to creating a new commit (i.e. not an amend commit)
view.newCommitRadio.setChecked (True)
self.__set_branch_ui_items()
self.cb_rescan (model)
#####################################################################
# MODEL ACTIONS
#####################################################################
def action_staged (self, model, *args):
'''This action is called when the model's staged list
changes. This is a thin wrapper around update_list_widget.'''
list_widget = self.view.stagedList
staged = model.get_staged()
self.__update_list_widget (list_widget, staged, True)
def action_unstaged (self, model, *args):
'''This action is called when the model's unstaged list
changes. This is a thin wrapper around update_list_widget.'''
list_widget = self.view.unstagedList
unstaged = model.get_unstaged()
self.__update_list_widget (list_widget, unstaged, False)
if self.view.untrackedCheckBox.isChecked():
untracked = model.get_untracked()
self.__update_list_widget (list_widget, untracked,
append=True,
staged=False,
untracked=True)
#####################################################################
# CALLBACKS
#####################################################################
def cb_browse_current (self, model):
print "MODEL::::::::::", model
def cb_browse_other (self, model):
print "MODEL::::::::::", model
def cb_cherry_pick (self, model, *args):
'''Starts a cherry-picking session.'''
(revs, summaries) = ugitcmds.git_log (all=True)
selection, idxs = self.__select_commits (revs, summaries)
if not selection: return
output = ugitcmds.git_cherry_pick (selection)
self.__show_command_output (output, model)
def cb_commit (self, model, *args):
'''Sets up data and calls ugitcmds.commit.'''
msg = model.get_commitmsg()
amend = self.view.amendRadio.isChecked()
commit_all = self.view.commitAllCheckBox.isChecked()
files = []
if not commit_all:
files = self.__get_selection_from_view (
self.view.stagedList,
model.get_staged() )
# Perform the commit
output = ugitcmds.git_commit (msg, amend, commit_all, files)
# Reset the commitmsg and rescan changes
if not output.startswith ('ERROR'):
model.set_commitmsg ('')
self.__show_command_output (output, model)
def cb_commit_all (self, model, *args):
'''Sets the commit-all checkbox and runs cb_commit.'''
self.view.commitAllCheckBox.setChecked (True)
self.cb_commit (model, *args)
def cb_commit_selected (self, model, *args):
'''Unsets the commit-all checkbox and runs cb_commit.'''
self.view.commitAllCheckBox.setChecked (False)
self.cb_commit (model, *args)
def cb_commit_sha1_selected (self, browser, revs):
'''This callback is called when a commit browser's
item is selected. This callback puts the current
revision sha1 into the commitText field.
This callback also puts shows the commit in the
browser's commit textedit and copies it into
the global clipboard/selection.'''
current = browser.commitList.currentRow()
item = browser.commitList.item (current)
if not item.isSelected():
browser.commitText.setText ('')
browser.revisionLine.setText ('')
return
# Get the commit's sha1 and put it in the revision line
sha1 = revs[current]
browser.revisionLine.setText (sha1)
browser.revisionLine.selectAll()
# Lookup the info for that sha1 and display it
commit_info = ugitcmds.git_show (sha1, color=True)
html = ugitutils.ansi_to_html (commit_info)
browser.commitText.setText (html)
# Copy the sha1 into the clipboard
QtGui.qApp.clipboard().setText (sha1, QClipboard.Clipboard)
QtGui.qApp.clipboard().setText (sha1, QClipboard.Selection)
def cb_diff_staged (self, model):
list_widget = self.view.stagedList
row, selected = self.__get_selected_row (list_widget)
if not selected:
self.view.displayText.setText ('')
return
filename = model.get_staged()[row]
diff = ugitcmds.git_diff (filename, staged=True)
html = ugitutils.ansi_to_html (diff)
if os.path.exists (filename):
pre = ugitutils.html_header ('Staged for commit')
else:
pre = ugitutils.html_header ('Staged for removal')
self.view.displayText.setText (pre + html)
def cb_diff_unstaged (self, model):
list_widget = self.view.unstagedList
row, selected = self.__get_selected_row (list_widget)
if not selected:
self.view.displayText.setText ('')
return
filename = (model.get_unstaged() + model.get_untracked())[row]
if os.path.isdir (filename):
pre = ugitutils.html_header ('Untracked directory')
cmd = 'ls -la %s' % ugitutils.shell_quote (filename)
html = '<pre>%s</pre>' % commands.getoutput (cmd)
self.view.displayText.setText ( pre + html )
return
if filename in model.get_unstaged():
diff = ugitcmds.git_diff (filename, staged=False)
pre = ugitutils.html_header ('Modified, unstaged')
html = ugitutils.ansi_to_html (diff)
else:
# untracked file
cmd = 'file -b %s' % ugitutils.shell_quote (filename)
file_type = commands.getoutput (cmd)
if 'binary' in file_type or 'data' in file_type:
sq_filename = ugitutils.shell_quote (filename)
cmd = 'hexdump -C %s' % sq_filename
contents = commands.getoutput (cmd)
else:
file = open (filename, 'r')
contents = ugitutils.html_encode (file.read())
file.close()
header = 'Untracked file: ' + file_type
pre = ugitutils.html_header (header)
html = '<pre>%s</pre>' % contents
self.view.displayText.setText (pre + html)
def cb_export_patches (self, model, *args):
'''Launches the commit browser and exports the selected
patches.'''
(revs, summaries) = ugitcmds.git_log ()
selection, idxs = self.__select_commits (revs, summaries)
if not selection: return
# now get the selected indices to determine whether
# a range of consecutive commits were selected
selected_range = range (idxs[0], idxs[-1] + 1)
export_range = len (idxs) > 1 and idxs == selected_range
output = ugitcmds.git_format_patch (selection, export_range)
self.__show_command_output (output)
def cb_get_commit_msg (self, model, *args):
(revs, summaries) = ugitcmds.git_log (oneline=True)
commit_msg = []
commit_lines = ugitcmds.git_show (revs[0]).split ('\n')
for idx, msg in enumerate (commit_lines):
if idx < 4: continue
msg = msg.lstrip()
if msg.startswith ('diff --git'):
commit_msg.pop()
break
commit_msg.append (msg)
model.set_commitmsg ('\n'.join (commit_msg).rstrip())
def cb_rescan (self, model, *args):
'''Populates view widgets with results from "git status."'''
# This allows us to defer notification until the
# we finish processing data
model.set_notify(False)
# Reset the staged and unstaged model lists
# NOTE: the model's unstaged list is used to
# hold both unstaged and untracked files.
model.staged = []
model.unstaged = []
model.untracked = []
# Read git status items
( staged_items,
unstaged_items,
untracked_items ) = ugitcmds.git_status()
# Gather items to be committed
for staged in staged_items:
if staged not in model.get_staged():
model.add_staged (staged)
# Gather unindexed items
for unstaged in unstaged_items:
if unstaged not in model.get_unstaged():
model.add_unstaged (unstaged)
# Gather untracked items
for untracked in untracked_items:
if untracked not in model.get_untracked():
model.add_untracked (untracked)
# Re-enable notifications and emit changes
model.set_notify(True)
model.notify_observers ('staged', 'unstaged')
def cb_show_diffstat (self, model, *args):
'''Show the diffstat from the latest commit.'''
output = ugitutils.ansi_to_html (ugitcmds.git_diff_stat())
doc = ugitutils.html_document (output)
self.__show_command_output (doc, rescan=False)
def cb_signoff (self, model, *args):
'''Adds a standard Signed-off by: tag to the end
of the current commit message.'''
msg = model.get_commitmsg()
signoff = 'Signed-off by: %s <%s>' % (
model.get_name(), model.get_email() )
if signoff not in msg:
model.set_commitmsg( '%s\n\n%s' % ( msg, signoff ) )
def cb_stage_changed (self, model, *args):
'''Stage all changed files for commit.'''
output = ugitcmds.git_add (model.get_unstaged())
self.__show_command_output (output, model)
def cb_stage_selected (self, model, *args):
'''Use "git add" to add items to the git index.
This is a thin wrapper around __apply_to_list.'''
command = ugitcmds.git_add_or_remove
widget = self.view.unstagedList
items = model.get_unstaged() + model.get_untracked()
self.__apply_to_list (command, model, widget, items)
def cb_stage_untracked (self, model, *args):
'''Stage all untracked files for commit.'''
output = ugitcmds.git_add (model.get_untracked())
self.__show_command_output (output, model)
def cb_unstage_all (self, model, *args):
'''Use "git reset" to remove all items from the git index.'''
output = ugitcmds.git_reset (model.get_staged())
self.__show_command_output (output, model)
def cb_unstage_selected (self, model, *args):
'''Use "git reset" to remove items from the git index.
This is a thin wrapper around __apply_to_list.'''
command = ugitcmds.git_reset
widget = self.view.stagedList
items = model.get_staged()
self.__apply_to_list (command, model, widget, items)
def cb_viz_all (self, model):
'''Visualizes the entire git history using gitk.'''
os.system ('gitk --all &')
def cb_viz_current (self, model):
'''Visualizes the current branch's history using gitk.'''
branch = ugitcmds.git_current_branch()
os.system ('gitk %s &' % ugitutils.shell_quote (branch))
#####################################################################
# PRIVATE HELPER METHODS
#####################################################################
def __apply_to_list (self, command, model, widget, items):
'''This is a helper method that retrieves the current
selection list, applies a command to that list,
displays a dialog showing the output of that command,
and calls cb_rescan to pickup changes.'''
apply_items = self.__get_selection_from_view (widget, items)
output = command (apply_items)
self.__show_command_output (output, model)
def __file_to_widget_item (self, filename, staged, untracked=False):
'''Given a filename, return a QListWidgetItem suitable
for adding to a QListWidget. "staged" controls whether
to use icons for the staged or unstaged list widget.'''
if staged:
icon_file = ugitutils.get_staged_icon (filename)
elif untracked:
icon_file = ugitutils.get_untracked_icon()
else:
icon_file = ugitutils.get_icon (filename)
icon = QIcon (QPixmap (icon_file))
item = QListWidgetItem()
item.setText (filename)
item.setIcon (icon)
return item
def __get_selected_row (self, list_widget):
row = list_widget.currentRow()
item = list_widget.item (row)
selected = item.isSelected()
return (row, selected)
def __get_selection_from_view (self, list_widget, items):
'''Returns an array of model items that correspond to
the selected QListWidget indices.'''
selected = []
for idx in range (list_widget.count()):
item = list_widget.item (idx)
if item.isSelected():
selected.append (items[idx])
return selected
def __select_commits (self, revs, summaries):
'''Use the GitCommitBrowser to select commits from a list.'''
if not summaries:
msg = 'ERROR: No commits exist in this branch.'''
self.__show_command_output (output=msg)
return ([],[])
browser = GitCommitBrowser (self.view)
QObject.connect ( browser.commitList,
SIGNAL ('itemSelectionChanged()'),
lambda: self.cb_commit_sha1_selected(
browser, revs) )
for summary in summaries:
browser.commitList.addItem (summary)
browser.show()
result = browser.exec_()
if result != QDialog.Accepted:
return ([],[])
list_widget = browser.commitList
selection = self.__get_selection_from_view (list_widget, revs)
if not selection: return ([],[])
# also return the selected index numbers
index_nums = range (len (revs))
idxs = self.__get_selection_from_view (list_widget, index_nums)
return (selection, idxs)
def __set_branch_ui_items (self):
'''Sets up items that mention the current branch name.'''
current_branch = ugitcmds.git_current_branch()
menu_text = 'Browse ' + current_branch + ' branch'
self.view.browseBranch.setText (menu_text)
status_text = 'Current branch: ' + current_branch
self.view.statusBar().showMessage (status_text)
def __show_command_output (self, output, model=None, rescan=True):
'''Shows output in a GitCommandDialog and optionally
rescans for changes.'''
dialog = GitCommandDialog (self.view, output=output)
dialog.show()
dialog.exec_()
if rescan and model: self.cb_rescan (model)
def __update_list_widget (self, list_widget, items,
staged, untracked=False, append=False):
'''A helper method to populate a QListWidget with the
contents of modelitems.'''
if not append:
list_widget.clear()
for item in items:
qitem = self.__file_to_widget_item (item,
staged, untracked)
list_widget.addItem( qitem )
-15
View File
@@ -1,15 +0,0 @@
import commands
from model import Model
def get_config(key):
return commands.getoutput('git config --get "%s"' % key)
class GitModel(Model):
def __init__ (self): Model.__init__(self, {
'commitmsg': '',
'staged': [],
'unstaged': [],
'untracked': [],
'name': get_config('user.name'),
'email': get_config('user.email'),
})
-429
View File
@@ -1,429 +0,0 @@
#!/usr/bin/env python
import os
import re
import commands
KNOWN_FILE_TYPES = {
'ascii c': 'c.png',
'python': 'script.png',
'ruby': 'script.png',
'shell': 'script.png',
'perl': 'script.png',
'java': 'script.png',
'assembler': 'binary.png',
'binary': 'binary.png',
'byte': 'binary.png',
'image': 'image.png',
}
ICONSDIR = os.path.join (os.path.dirname (__file__), 'icons')
def ident_file_type (filename):
'''Returns an icon based on the contents of filename.'''
if os.path.exists (filename):
quoted_filename = shell_quote (filename)
fileinfo = commands.getoutput('file -b %s' % quoted_filename)
for filetype, iconname in KNOWN_FILE_TYPES.iteritems():
if filetype in fileinfo.lower():
return iconname
else:
return 'removed.png'
# Fallback for modified files of an unknown type
return 'generic.png'
def get_icon (filename):
'''Returns the full path to an icon file corresponding to
filename's contents.'''
icon_file = ident_file_type (filename)
return os.path.join (ICONSDIR, icon_file)
def get_staged_icon (filename):
'''Special-case method for staged items. These are only
ever 'staged' and 'removed' items in the staged list.'''
if os.path.exists (filename):
return os.path.join (ICONSDIR, 'staged.png')
else:
return os.path.join (ICONSDIR, 'removed.png')
def get_untracked_icon():
return os.path.join (ICONSDIR, 'untracked.png')
def shell_quote (*inputs):
'''Quote strings so that they can be suitably martialled
off to the shell. This method supports POSIX sh syntax.
This is crucial to properly handle command line arguments
with spaces, quotes, double-quotes, etc.'''
regex = re.compile ('[^\w!%+,\-./:@^]')
quote_regex = re.compile ("((?:'\\''){2,})")
ret = []
for input in inputs:
if not input:
continue
if '\x00' in input:
raise AssertionError, ('No way to quote strings '
'containing null (\\000) bytes')
# = does need quoting else in command position it's a
# program-local environment setting
match = regex.search (input)
if match:
# ' -> '\''
input = input.replace ("'", "'\\''")
# make multiple ' in a row look simpler
# '\'''\'''\'' -> '"'''"'
quote_match = quote_regex.match (input)
if quote_match:
quotes = match.group (1)
input.replace (quotes,
("'" * (len(quotes)/4)) + "\"'")
input = "'%s'" % input
if input.startswith ("''"):
input = input.lstrip ("''")
if input.endswith ("''"):
input = input.rstrip ("''")
ret.append (input)
return ' '.join (ret)
ANSI_BACKGROUND_COLOR = 41
ANSI_TABLE = {
'1': 'grey',
'30': 'black',
'31': 'red',
'32': 'green',
'33': 'yellow',
'34': 'blue',
'35': 'magenta',
'36': 'cyan',
'37': 'white',
}
def html_document (html_fragment):
return '<body>%s</body>' % html_fragment
def ansi_to_html (ansi):
'''Converts a block of text into an equivalent html fragment.'''
html = []
regex = re.compile ('(.*?)\x1b\[(\d*)m')
for line in ansi.split ('\n'):
linecopy = str (html_encode(line))
match = regex.match (linecopy)
tagged = False
while match:
start, end = match.span()
prefix = match.group (1)
middle = match.group (2)
postfix = linecopy[end:]
if middle in ANSI_TABLE:
color = ANSI_TABLE[middle]
middle = '<span style="color: %s">' % color
tagged = True
elif middle == str (ANSI_BACKGROUND_COLOR):
middle = '<span style="background-color:red">'
tagged = True
else:
if tagged:
middle = '</span>'
else:
middle = ''
linecopy = str (prefix + middle + postfix)
match = regex.match (linecopy)
html.append (linecopy)
return '<br />\n'.join (html)
def html_header (header):
return '''
<p style="color: black;
background-color: yellow">
%s
</p>''' % header
def html_encode (ascii):
html = []
ANSI = [ '\x1b', '[' ]
for char in ascii:
if char == '\t':
html.append ( '&nbsp;' * 8 )
continue
if char not in ANSI and not char.isalnum():
html.append (ENTITIES[char])
else:
html.append (char)
return ''.join (html)
# Keep this at the bottom of the file since it's generated output.
# Generated by html2py.pl from HTML::Entities on Wed Dec 5 16:28:55 PST 2007
ENTITIES = {
chr(0) : '&#0;',
chr(1) : '&#1;',
chr(2) : '&#2;',
chr(3) : '&#3;',
chr(4) : '&#4;',
chr(5) : '&#5;',
chr(6) : '&#6;',
chr(7) : '&#7;',
chr(8) : '&#8;',
chr(9) : '&#9;',
chr(10) : '&#10;',
chr(11) : '&#11;',
chr(12) : '&#12;',
chr(13) : '&#13;',
chr(14) : '&#14;',
chr(15) : '&#15;',
chr(16) : '&#16;',
chr(17) : '&#17;',
chr(18) : '&#18;',
chr(19) : '&#19;',
chr(20) : '&#20;',
chr(21) : '&#21;',
chr(22) : '&#22;',
chr(23) : '&#23;',
chr(24) : '&#24;',
chr(25) : '&#25;',
chr(26) : '&#26;',
chr(27) : '&#27;',
chr(28) : '&#28;',
chr(29) : '&#29;',
chr(30) : '&#30;',
chr(31) : '&#31;',
chr(32) : '&#32;',
chr(33) : '&#33;',
chr(34) : '&quot;',
chr(35) : '&#35;',
chr(36) : '&#36;',
chr(37) : '&#37;',
chr(38) : '&amp;',
chr(39) : '&#39;',
chr(40) : '&#40;',
chr(41) : '&#41;',
chr(42) : '&#42;',
chr(43) : '&#43;',
chr(44) : '&#44;',
chr(45) : '&#45;',
chr(46) : '&#46;',
chr(47) : '&#47;',
chr(48) : '&#48;',
chr(49) : '&#49;',
chr(50) : '&#50;',
chr(51) : '&#51;',
chr(52) : '&#52;',
chr(53) : '&#53;',
chr(54) : '&#54;',
chr(55) : '&#55;',
chr(56) : '&#56;',
chr(57) : '&#57;',
chr(58) : '&#58;',
chr(59) : '&#59;',
chr(60) : '&lt;',
chr(61) : '&#61;',
chr(62) : '&gt;',
chr(63) : '&#63;',
chr(64) : '&#64;',
chr(65) : '&#65;',
chr(66) : '&#66;',
chr(67) : '&#67;',
chr(68) : '&#68;',
chr(69) : '&#69;',
chr(70) : '&#70;',
chr(71) : '&#71;',
chr(72) : '&#72;',
chr(73) : '&#73;',
chr(74) : '&#74;',
chr(75) : '&#75;',
chr(76) : '&#76;',
chr(77) : '&#77;',
chr(78) : '&#78;',
chr(79) : '&#79;',
chr(80) : '&#80;',
chr(81) : '&#81;',
chr(82) : '&#82;',
chr(83) : '&#83;',
chr(84) : '&#84;',
chr(85) : '&#85;',
chr(86) : '&#86;',
chr(87) : '&#87;',
chr(88) : '&#88;',
chr(89) : '&#89;',
chr(90) : '&#90;',
chr(91) : '&#91;',
chr(92) : '&#92;',
chr(93) : '&#93;',
chr(94) : '&#94;',
chr(95) : '&#95;',
chr(96) : '&#96;',
chr(97) : '&#97;',
chr(98) : '&#98;',
chr(99) : '&#99;',
chr(100) : '&#100;',
chr(101) : '&#101;',
chr(102) : '&#102;',
chr(103) : '&#103;',
chr(104) : '&#104;',
chr(105) : '&#105;',
chr(106) : '&#106;',
chr(107) : '&#107;',
chr(108) : '&#108;',
chr(109) : '&#109;',
chr(110) : '&#110;',
chr(111) : '&#111;',
chr(112) : '&#112;',
chr(113) : '&#113;',
chr(114) : '&#114;',
chr(115) : '&#115;',
chr(116) : '&#116;',
chr(117) : '&#117;',
chr(118) : '&#118;',
chr(119) : '&#119;',
chr(120) : '&#120;',
chr(121) : '&#121;',
chr(122) : '&#122;',
chr(123) : '&#123;',
chr(124) : '&#124;',
chr(125) : '&#125;',
chr(126) : '&#126;',
chr(127) : '&#127;',
chr(128) : '&#128;',
chr(129) : '&#129;',
chr(130) : '&#130;',
chr(131) : '&#131;',
chr(132) : '&#132;',
chr(133) : '&#133;',
chr(134) : '&#134;',
chr(135) : '&#135;',
chr(136) : '&#136;',
chr(137) : '&#137;',
chr(138) : '&#138;',
chr(139) : '&#139;',
chr(140) : '&#140;',
chr(141) : '&#141;',
chr(142) : '&#142;',
chr(143) : '&#143;',
chr(144) : '&#144;',
chr(145) : '&#145;',
chr(146) : '&#146;',
chr(147) : '&#147;',
chr(148) : '&#148;',
chr(149) : '&#149;',
chr(150) : '&#150;',
chr(151) : '&#151;',
chr(152) : '&#152;',
chr(153) : '&#153;',
chr(154) : '&#154;',
chr(155) : '&#155;',
chr(156) : '&#156;',
chr(157) : '&#157;',
chr(158) : '&#158;',
chr(159) : '&#159;',
chr(160) : '&nbsp;',
chr(161) : '&iexcl;',
chr(162) : '&cent;',
chr(163) : '&pound;',
chr(164) : '&curren;',
chr(165) : '&yen;',
chr(166) : '&brvbar;',
chr(167) : '&sect;',
chr(168) : '&uml;',
chr(169) : '&copy;',
chr(170) : '&ordf;',
chr(171) : '&laquo;',
chr(172) : '&not;',
chr(173) : '&shy;',
chr(174) : '&reg;',
chr(175) : '&macr;',
chr(176) : '&deg;',
chr(177) : '&plusmn;',
chr(178) : '&sup2;',
chr(179) : '&sup3;',
chr(180) : '&acute;',
chr(181) : '&micro;',
chr(182) : '&para;',
chr(183) : '&middot;',
chr(184) : '&cedil;',
chr(185) : '&sup1;',
chr(186) : '&ordm;',
chr(187) : '&raquo;',
chr(188) : '&frac14;',
chr(189) : '&frac12;',
chr(190) : '&frac34;',
chr(191) : '&iquest;',
chr(192) : '&Agrave;',
chr(193) : '&Aacute;',
chr(194) : '&Acirc;',
chr(195) : '&Atilde;',
chr(196) : '&Auml;',
chr(197) : '&Aring;',
chr(198) : '&AElig;',
chr(199) : '&Ccedil;',
chr(200) : '&Egrave;',
chr(201) : '&Eacute;',
chr(202) : '&Ecirc;',
chr(203) : '&Euml;',
chr(204) : '&Igrave;',
chr(205) : '&Iacute;',
chr(206) : '&Icirc;',
chr(207) : '&Iuml;',
chr(208) : '&ETH;',
chr(209) : '&Ntilde;',
chr(210) : '&Ograve;',
chr(211) : '&Oacute;',
chr(212) : '&Ocirc;',
chr(213) : '&Otilde;',
chr(214) : '&Ouml;',
chr(215) : '&times;',
chr(216) : '&Oslash;',
chr(217) : '&Ugrave;',
chr(218) : '&Uacute;',
chr(219) : '&Ucirc;',
chr(220) : '&Uuml;',
chr(221) : '&Yacute;',
chr(222) : '&THORN;',
chr(223) : '&szlig;',
chr(224) : '&agrave;',
chr(225) : '&aacute;',
chr(226) : '&acirc;',
chr(227) : '&atilde;',
chr(228) : '&auml;',
chr(229) : '&aring;',
chr(230) : '&aelig;',
chr(231) : '&ccedil;',
chr(232) : '&egrave;',
chr(233) : '&eacute;',
chr(234) : '&ecirc;',
chr(235) : '&euml;',
chr(236) : '&igrave;',
chr(237) : '&iacute;',
chr(238) : '&icirc;',
chr(239) : '&iuml;',
chr(240) : '&eth;',
chr(241) : '&ntilde;',
chr(242) : '&ograve;',
chr(243) : '&oacute;',
chr(244) : '&ocirc;',
chr(245) : '&otilde;',
chr(246) : '&ouml;',
chr(247) : '&divide;',
chr(248) : '&oslash;',
chr(249) : '&ugrave;',
chr(250) : '&uacute;',
chr(251) : '&ucirc;',
chr(252) : '&uuml;',
chr(253) : '&yacute;',
chr(254) : '&thorn;',
chr(255) : '&yuml;',
}
-38
View File
@@ -1,38 +0,0 @@
import os
from PyQt4 import QtCore, QtGui
from PyQt4.QtCore import SIGNAL
from ugitWindow import Ui_ugitWindow
from ugitCommandDialog import Ui_ugitCommandDialog
from ugitCommitBrowser import Ui_ugitCommitBrowser
class GitView (Ui_ugitWindow, QtGui.QMainWindow):
'''The main ugit interface.'''
def __init__ (self, parent=None, autosetup=True):
QtGui.QMainWindow.__init__ (self, parent)
Ui_ugitWindow.__init__ (self)
if autosetup:
self.setupUi (self)
class GitCommandDialog (Ui_ugitCommandDialog, QtGui.QDialog):
'''A simple dialog to display command output.'''
def __init__ (self, parent=None, output=None, autosetup=True):
QtGui.QDialog.__init__ (self, parent)
Ui_ugitCommandDialog.__init__ (self)
if autosetup:
self.setupUi (self)
if output is not None:
self.set_command_output (output)
def set_command_output (self, output):
self.commandText.setText (output)
class GitCommitBrowser (Ui_ugitCommitBrowser, QtGui.QDialog):
'''A dialog to display commits in for selection.'''
def __init__ (self, parent=None, autosetup=True):
QtGui.QDialog.__init__ (self, parent)
Ui_ugitCommitBrowser.__init__ (self)
if autosetup:
self.setupUi (self)
# Make the commit list slighty larger than the
# commit message widget
self.splitter.setSizes ([ 50, 200 ])
-5
View File
@@ -1,5 +0,0 @@
#!/usr/bin/env python
def build(bld):
pyqt = bld.create_obj ('py')
pyqt.inst_var = 'PYMODS_UGIT'
pyqt.find_sources_in_dirs ('.')
+17
View File
@@ -0,0 +1,17 @@
#!/bin/sh
. $(dirname $0)/common.sh
PREFIX=installroot
BINDIR=$PREFIX/bin
DOCDIR=$PREFIX/share/doc/cola
try_python /usr/bin/python2.5 "$PREFIX"
# no symlinks on win32
rm -f $BINDIR/bin/cola
cp scripts/py2exe-* $BINDIR
cp scripts/cola-win32.sh $BINDIR
mkdir -p $DOCDIR
cp README $DOCDIR/README.txt
+6
View File
@@ -0,0 +1,6 @@
#!/bin/sh
# This should work if used with both the default Python2.5
# from python.org and the default PyQt4 binary package
# from Riverbank. Adjust accordingly.
DIR=$(dirname "$0")
c:/Python25/python.exe "$DIR"/git-cola
+11
View File
@@ -0,0 +1,11 @@
[Desktop Entry]
Encoding=UTF-8
Name=cola
Comment=cola: a highly caffeinated git gui
Exec=git cola
Icon=/usr/share/cola/icons/git.png
StartupNotify=true
Terminal=false
Type=Application
Categories=Development
X-KDE-SubstituteUID=false
+48
View File
@@ -0,0 +1,48 @@
#!/bin/sh
waf_backup()
{
if [ -e .lock-wscript ]; then
mv .lock-wscript .lock-wscript.old
fi
}
waf_restore()
{
if [ -e .lock-wscript.old ]; then
mv .lock-wscript.old .lock-wscript
fi
}
try_python()
{
waf_backup
echo "+------------------------------------------------"
echo "+ PYTHON=$1"
echo "+------------------------------------------------"
if [ ! -x "$1" ]; then
echo "$1 is not executable."
exit -1
fi
env PYTHON="$1" \
./configure \
--prefix="$2" \
--blddir="$PWD/tmp.$$" &&
make &&
make install
status=$?
rm -rf "$PWD/tmp.$$"
if test -z $status; then
echo "exited with status code $status"
exit $status
fi
waf_restore
}
cd $(dirname $0)
cd ..
VERSION=$(scripts/version.sh)
+13
View File
@@ -0,0 +1,13 @@
#!/bin/sh
VN=$(git describe HEAD 2>/dev/null)
VN=$(echo "$VN" | sed -e 's/-/./g')
LF='
'
case "$VN" in
*$LF*) (exit 1) ;;
v[0-9]*)
test -z "$(git diff-index --name-only HEAD)" ||
VN="$VN-dirty" ;;
esac
VN=$(expr "$VN" : v*'\(.*\)')
echo "VERSION='$VN'"
-27
View File
@@ -1,27 +0,0 @@
#!/usr/bin/env perl
use HTML::Entities;
my $date = `date`;
chomp $date;
print "# Generated by html2py.pl from HTML::Entities on $date\n";
print "ENTITIES = {\n";
for my $k (sort keys %HTML::Entities::char2entity) {
my $int = ord($k);
if ( $int > 255 ) {
# These are either:
# - outside of ord()'s 0-255 range
# - used in ANSI color sequences handled by
# ugitutils` ansi_to_html.
next;
}
my $v = $HTML::Entities::char2entity{$k};
print "\t";
print "chr($int)";
print "\t:\t";
print "'$v',\n";
}
print "}\n";
+17
View File
@@ -0,0 +1,17 @@
#!/bin/sh
. $(dirname $0)/common.sh
waf_backup
dpkg-buildpackage -A
waf_restore
rm -rf debian/bld
rm -rf debian/cola
rm ../cola_*.changes
mv ../cola_*.deb .
alien --to-rpm cola_*.deb
if [ -d $HOME/htdocs/cola/releases ];
then
mv cola_*.deb cola-*.rpm $HOME/htdocs/cola/releases
fi
+25
View File
@@ -0,0 +1,25 @@
#!/bin/sh
. $(dirname $0)/common.sh
VERSION=$(scripts/version.sh)
BASENAME=cola-win32-$VERSION
FILE=$BASENAME.tar.gz
DIR=installroot
if [ -d $DIR ]; then
echo "ERROR: '$DIR' already exists."
exit -1
fi
scripts/build-win32.sh || exit -1
if [ -e $BASENAME ]; then
echo "error: $BASENAME exists"
exit -1
fi
rsync -avr $DIR/ $BASENAME/
tar czf $FILE $BASENAME/
rm -rf $DIR $BASENAME
if [ -e $HOME/htdocs/cola/releases/win32 ]; then
mv -v $FILE $HOME/htdocs/cola/releases/win32
fi
+19
View File
@@ -0,0 +1,19 @@
#!/bin/sh
. $(dirname $0)/common.sh
BASENAME=cola-$VERSION
FILE=$BASENAME.tar.gz
DIR=installroot
if [ -d $DIR ]; then
echo "ERROR: '$DIR' already exists"
exit -1
fi
try_python "$(which python)" "$DIR"
rsync -avr $DIR/ $BASENAME/ &&
tar czf $FILE $BASENAME/ &&
rm -rf $DIR $BASENAME
if [ -d $HOME/htdocs/cola/releases ]; then
mv -v $FILE $HOME/htdocs/cola/releases
fi
+1
View File
@@ -0,0 +1 @@
@python py2exe-setup.py py2exe
+15
View File
@@ -0,0 +1,15 @@
from distutils.core import setup
import py2exe
setup(
windows = [
{
"script" : "git-cola",
},
],
options = {
"py2exe" : {
"includes" : ["PyQt4._qt", "pprint", "sip"],
}
}
)
+6
View File
@@ -0,0 +1,6 @@
#!/usr/bin/env python
import os
import sys
import py_compile
for filename in sys.argv[1:]:
py_compile.compile(filename, doraise=True)
+6
View File
@@ -0,0 +1,6 @@
#!/bin/sh
VERSION=$(git describe HEAD 2>/dev/null)
VERSION=$(echo "$VERSION" | sed -e 's/^v//')
VERSION=$(echo "$VERSION" | sed -e 's/-/./g')
VERSION=$(echo "$VERSION" | perl -p -e 's/(\d+\.\d+\.\d+)(\.[^.]+)$/\1/')
echo $VERSION
+19
View File
@@ -0,0 +1,19 @@
#!/usr/bin/env python
import os
import Common
import Params
from os.path import exists
from os.path import join
def gen_install(path):
def install():
Common.install_files('COLA_MODULES', '', path)
return install
def build(bld):
vgen = bld.create_obj('command-output')
vgen.command = 'gitversion.sh'
vgen.stdin = 'gitversion.sh'
vgen.stdout = 'version.py'
vgen.install = gen_install('../scripts/version.py')
Common.install_files('COLA_APPS', '', 'cola.desktop')
Executable
+78
View File
@@ -0,0 +1,78 @@
#!/bin/sh
cd $(dirname $0)
ERROR=''
COLA=$(which cola)
test -z "$COLA" && ERROR=1
if [ $ERROR ]; then
echo "cola must be in your path in order to call run_tests directly."
exit -1
fi
BINDIR=$(dirname $COLA)
PREFIX=$(dirname $BINDIR)
PYTHONPATH="$PREFIX"/share:"$PREFIX"/share/cola:"$PYTHONPATH"
export PYTHONPATH
(
for unittest in t[0-9]*.py; do
echo
echo -n "$unittest "
./$unittest
if [ $? -ne 0 ] && [ -z "$RUN_ALL" ]; then
echo "$unittest failed with exit code $?"
exit -1
fi
done
) 2>&1 | perl -e '
use Time::HiRes qw(gettimeofday);
my ($start_sec, $start_ms) = gettimeofday();
my $count = 0;
my @fail = ();
while(<>) {
print;
if ( /(\d+) tests/ ) {
$count += $1;
}
elsif ( /(.*) failed/ ) {
push @fail, $1;
}
}
my ($end_sec, $end_ms) = gettimeofday();
my $min = 0;
my $sec = $end_sec - $start_sec;
my $secfrac = ( $end_ms - $start_ms ) * 100;
while ( $secfrac < 0.0 ) {
$secfrac += 1000 * 100;
}
$secfrac = substr("$secfrac", 0, 2);
while ( $sec > 60 ) {
$sec -= 60;
$min++;
}
if ( @fail ) {
print "\n";
print "The following test FAILED:\n";
print "-"x70 ."\n\t";
print join("\n\t", @fail);
print "\n";
}
print "\n";
print `../scripts/gitversion.sh`;
print "-"x70 ."\n";
print "$count tests total\n";
print "$min min $sec.$secfrac sec total runtime\n\n";
'
exit 0
+34
View File
@@ -0,0 +1,34 @@
#!/usr/bin/env python
import unittest
import os
import imp
from testutils import *
from testmodel import *
class ImportTest(TestCase):
pass
def setup_tests():
for module in """
cola.model
cola.models
cola.qobserver
cola.controllers
cola.utils
cola.qtutils
""".split():
def import_test(self):
modinfo = None
for idx, path in enumerate(module.split('.')):
if idx == 0:
modinfo = imp.find_module(path)
mod = imp.load_module(module, *modinfo)
else:
modinfo = imp.find_module(path, modinfo[0])
mod = imp.load_module(path, *modinfo)
self.failUnless( mod )
setattr(ImportTest, "test" + module.title(), import_test)
setup_tests()
unittest.main()
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env python
import os
import unittest
import testutils
from testmodel import TestModel
from cola import git
class GitCommandTest(unittest.TestCase):
def setUp(self):
self.git = git.Git()
def testGitVersion(self):
version = self.git.version()
self.failUnless( version.startswith('git version') )
def testGitTag(self):
tags = self.git.tag().splitlines()
self.failUnless( 'v0.0' in tags )
def testGitShow(self):
id = '1b9742bda5d26a4f250fa64657f66ed20624a084'
contents = self.git.show(id).splitlines()
self.failUnless( contents[0] == '/build' )
if __name__ == '__main__':
unittest.main()
+78
View File
@@ -0,0 +1,78 @@
#!/usr/bin/env python
import unittest
from testmodel import TestModel
class ModelTest(unittest.TestCase):
def setUp(self):
self.model = TestModel()
def tearDown(self):
del self.model
def testCreate(self):
self.model.create(foo='bar')
self.failUnless(self.model.get_foo()=='bar')
def testRegularAttributes(self):
"""Test accessing attribute via model.attribute."""
self.failUnless( self.model.attribute == 'value' )
def testMixedcaseAttributes(self):
"""Test accessing attribute via model.Attribute."""
self.failUnless( self.model.AtTrIbUte == 'value' )
def testExplicitAttr(self):
"""Test accessing an explicit attribute. Just in case we f** up getattr"""
self.failUnless( self.model.hello == 'world' )
def testRealMethod(self):
"""Test calling a concrete model method."""
self.failUnless( self.model.testMethod() == 'test' )
def testGetter(self):
"""Test calling using the get* method."""
self.failUnless( self.model.getAttribute() == 'value' )
def testSetter(self):
"""Test using the set* method."""
self.model.set_param('newAttribute','baz')
self.failUnless( self.model.newattribute == 'baz' )
self.failUnless( self.model.getNewAttribute() == 'baz' )
def testAddArray(self):
"""Test using the add* array method."""
self.model.array = []
self.model.addArray('bar')
self.failUnless( self.model.array[0] == 'bar' )
self.model.addArray('baz','quux')
self.failUnless( self.model.array[1] == 'baz' )
self.failUnless( self.model.array[2] == 'quux' )
def testAppendArray(self):
"""Test using the append* array method."""
self.model.array = []
self.model.appendArray('bar')
self.failUnless( self.model.array[0] == 'bar' )
self.model.appendArray('baz','quux')
self.failUnless( self.model.array[1] == 'baz' )
self.failUnless( self.model.array[2] == 'quux' )
def testDict(self):
"""Test setting dictionary/dictionary values."""
self.model.dict = { 'hello': 'world' }
self.failUnless( self.model.dict['hello'] == 'world' )
self.failUnless( self.model.getDict()['hello'] == 'world' )
def testFromDict(self):
"""Test reconstituting a model from a dictionary."""
self.model.from_dict({
'test_dict': { 'hello':'world' },
'test_list': [ 'foo', 'bar' ],
'test_str': 'foo',
})
self.failUnless( self.model.get_test_dict()['hello'] == 'world' )
self.failUnless( self.model.get_test_list()[1] == 'bar' )
if __name__ == '__main__':
unittest.main()
+55
View File
@@ -0,0 +1,55 @@
#!/usr/bin/env python
import os
import unittest
import testutils
from testutils import pipe
from cola import models
class GitOpsTest(testutils.TestCase):
def testCommit(self):
self.shell("""
echo A > A
echo B > B
git init 2>&1 > /dev/null
git add A B
""")
model = models.Model()
model.commit(m="commit test")
log = pipe("git log --pretty=oneline | wc -l")
self.failUnless( '1' == log )
def testConfig(self):
self.shell("""
git init 2>&1 >/dev/null
git config section.key value
""")
model = models.Model()
value = model.config('section.key', get=True)
self.failUnless( value == 'value' )
# Test config_set
model.config_set('section.bool', True)
value = model.config('section.bool', get=True)
self.failUnless( value == 'true' )
model.config_set('section.bool', False)
# Test config_dict
config_dict = model.config_dict(local=True)
self.failUnless( config_dict['section_key'] == 'value' )
self.failUnless( config_dict['section_bool'] == False )
# Test config_dict --global
global_dict = model.config_dict(local=False)
self.failUnless( type(global_dict) is dict )
if __name__ == '__main__':
unittest.main()
+55
View File
@@ -0,0 +1,55 @@
#!/usr/bin/env python
import os
import unittest
import testutils
from testmodel import InnerModel
from testmodel import NestedModel
from cola.model import Model
class SaveRestoreTest(testutils.TestCase):
def setUp(self):
testutils.TestCase.setUp(self)
self.nested = NestedModel()
path = os.path.join(self.testDir(), 'test.data')
self.nested.save(path)
# reconstitute
self.clone = Model.instance(path)
def testCloneToClass(self):
self.failUnless( str(NestedModel) ==
str(self.clone.__class__) )
def testClonedInnerToClass(self):
self.failUnless( str(InnerModel) ==
str(self.clone.inner.__class__) )
def testClonedListAndParam(self):
self.failUnless( str(self.clone.inner.__class__) ==
str(self.clone.innerList[0].__class__) )
def testList(self):
self.failUnless( str(InnerModel) ==
str(self.clone.innerList[1][0].__class__) )
def testListInList(self):
self.failUnless( str(InnerModel) ==
str(self.clone.innerList[2][0][0].__class__))
def testListInListInList(self):
self.failUnless( str(InnerModel) ==
str(self.clone.innerList[3][0][0][0].__class__))
def testDictInList(self):
self.failUnless( str(dict) ==
str(self.clone.innerList[4].__class__))
def testObjectInDictInList(self):
self.failUnless( str(InnerModel) ==
str(self.clone.innerList[-1]["foo"].__class__))
if __name__ == '__main__':
unittest.main()
Executable
+31
View File
@@ -0,0 +1,31 @@
#!/usr/bin/env python
import os
from cola.model import Model
class TestModel(Model):
def init(self):
duck = Model().create(sound='quack',name='ducky')
goose = Model().create(sound='cluck',name='goose')
self.create(attribute = 'value',
mylist=[duck,duck,goose])
self.hello = 'world'
self.set_mylist([duck,duck,goose, 'meow', 'caboose',42])
def testMethod(self):
return 'test'
class InnerModel(Model):
def init(self):
self.create(foo = 'bar')
class NestedModel(Model):
def init(self):
self.create(inner = InnerModel(),
innerList = [],
normaList = [ 1,2,3, [4,5, [6,7,8], 9]])
self.innerList.append(InnerModel())
self.innerList.append([InnerModel()])
self.innerList.append([[InnerModel()]])
self.innerList.append([[[InnerModel(),InnerModel()]]])
self.innerList.append({"foo": InnerModel()})
+70
View File
@@ -0,0 +1,70 @@
#!/usr/bin/env python
import os
import sys
import shutil
import unittest
from os.path import join
from os.path import dirname
from os.path import basename
TEST_SCRIPT_DIR = dirname(__file__)
ROOT_TMP_DIR = join( dirname(TEST_SCRIPT_DIR), 'tmp' )
TEST_TMP_DIR = join( ROOT_TMP_DIR, basename(sys.argv[0]) )
DEBUG_MODE = os.getenv("DEBUG",'')
def setup_dir(dir):
newdir = dir
parentdir = dirname(newdir)
if not os.path.isdir(parentdir):
os.mkdir(parentdir)
if not os.path.isdir(newdir):
os.mkdir(newdir)
LAST_IDX = -1
def test_dir():
global LAST_IDX
return '%s-%d.%04d' % (TEST_TMP_DIR, os.getpid(), LAST_IDX)
def create_test_dir():
global LAST_IDX
LAST_IDX += 1
newdir = test_dir()
setup_dir(newdir)
os.chdir(newdir)
return newdir
def remove_dir(dir):
if not DEBUG_MODE:
os.chdir(ROOT_TMP_DIR)
shutil.rmtree(dir)
def remove_test_dir():
global LAST_IDX
testdir = test_dir()
remove_dir(testdir)
LAST_IDX -= 1
def shell(cmd):
result = os.system(cmd)
return result
def pipe(cmd):
p = os.popen(cmd)
out = p.read().strip()
p.close()
return out
# All tests that operate on temporary data
# derive from testutils.TestCase
class TestCase(unittest.TestCase):
def setUp(self):
create_test_dir()
def tearDown(self):
remove_test_dir()
def shell(self, cmd):
result = shell(cmd)
self.failIf(result != 0)
def testDir(self):
return test_dir()
+108
View File
@@ -0,0 +1,108 @@
<ui version="4.0" >
<class>bookmark</class>
<widget class="QDialog" name="bookmark" >
<property name="geometry" >
<rect>
<x>0</x>
<y>0</y>
<width>531</width>
<height>513</height>
</rect>
</property>
<property name="windowTitle" >
<string>Bookmarks</string>
</property>
<layout class="QVBoxLayout" >
<item>
<widget class="QListWidget" name="bookmarks" >
<property name="alternatingRowColors" >
<bool>true</bool>
</property>
<property name="selectionMode" >
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" >
<item>
<widget class="QPushButton" name="button_open" >
<property name="text" >
<string>Open</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="button_delete" >
<property name="text" >
<string>Delete</string>
</property>
</widget>
</item>
<item>
<spacer>
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" >
<size>
<width>91</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="button_save" >
<property name="text" >
<string>Save</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="button_cancel" >
<property name="text" >
<string>Cancel</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>button_save</sender>
<signal>clicked()</signal>
<receiver>bookmark</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel" >
<x>398</x>
<y>490</y>
</hint>
<hint type="destinationlabel" >
<x>265</x>
<y>256</y>
</hint>
</hints>
</connection>
<connection>
<sender>button_cancel</sender>
<signal>clicked()</signal>
<receiver>bookmark</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel" >
<x>483</x>
<y>490</y>
</hint>
<hint type="destinationlabel" >
<x>265</x>
<y>256</y>
</hint>
</hints>
</connection>
</connections>
</ui>
+70
View File
@@ -0,0 +1,70 @@
<ui version="4.0" >
<class>combo</class>
<widget class="QDialog" name="combo" >
<property name="geometry" >
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>73</height>
</rect>
</property>
<property name="windowTitle" >
<string>Select Branch...</string>
</property>
<layout class="QVBoxLayout" >
<item>
<widget class="QComboBox" name="items_widget" />
</item>
<item>
<widget class="QDialogButtonBox" name="button_box" >
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons" >
<set>QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>items_widget</tabstop>
<tabstop>button_box</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>button_box</sender>
<signal>accepted()</signal>
<receiver>combo</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel" >
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel" >
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>button_box</sender>
<signal>rejected()</signal>
<receiver>combo</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel" >
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel" >
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>
+147
View File
@@ -0,0 +1,147 @@
<ui version="4.0" >
<class>commit</class>
<widget class="QDialog" name="commit" >
<property name="geometry" >
<rect>
<x>0</x>
<y>0</y>
<width>780</width>
<height>512</height>
</rect>
</property>
<layout class="QVBoxLayout" >
<item>
<widget class="QSplitter" name="splitter" >
<property name="sizePolicy" >
<sizepolicy vsizetype="MinimumExpanding" hsizetype="MinimumExpanding" >
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="orientation" >
<enum>Qt::Vertical</enum>
</property>
<property name="handleWidth" >
<number>2</number>
</property>
<widget class="QListWidget" name="commit_list" >
<property name="sizePolicy" >
<sizepolicy vsizetype="MinimumExpanding" hsizetype="MinimumExpanding" >
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize" >
<size>
<width>0</width>
<height>20</height>
</size>
</property>
<property name="font" >
<font>
<family>Monospace</family>
</font>
</property>
<property name="alternatingRowColors" >
<bool>true</bool>
</property>
<property name="selectionMode" >
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
</widget>
<widget class="QTextEdit" name="commit_text" >
<property name="sizePolicy" >
<sizepolicy vsizetype="MinimumExpanding" hsizetype="MinimumExpanding" >
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize" >
<size>
<width>0</width>
<height>40</height>
</size>
</property>
<property name="font" >
<font>
<family>Monospace</family>
<pointsize>12</pointsize>
</font>
</property>
<property name="tabChangesFocus" >
<bool>true</bool>
</property>
<property name="undoRedoEnabled" >
<bool>false</bool>
</property>
<property name="readOnly" >
<bool>true</bool>
</property>
</widget>
</widget>
</item>
<item>
<layout class="QHBoxLayout" >
<item>
<widget class="QLabel" name="label" >
<property name="text" >
<string>Revision Expression:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="revision" />
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="button_box" >
<property name="standardButtons" >
<set>QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>button_box</tabstop>
<tabstop>commit_list</tabstop>
<tabstop>revision</tabstop>
<tabstop>commit_text</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>button_box</sender>
<signal>accepted()</signal>
<receiver>commit</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel" >
<x>205</x>
<y>426</y>
</hint>
<hint type="destinationlabel" >
<x>205</x>
<y>223</y>
</hint>
</hints>
</connection>
<connection>
<sender>button_box</sender>
<signal>rejected()</signal>
<receiver>commit</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel" >
<x>205</x>
<y>426</y>
</hint>
<hint type="destinationlabel" >
<x>205</x>
<y>223</y>
</hint>
</hints>
</connection>
</connections>
</ui>
+306
View File
@@ -0,0 +1,306 @@
<ui version="4.0" >
<class>createbranch</class>
<widget class="QDialog" name="createbranch" >
<property name="geometry" >
<rect>
<x>0</x>
<y>0</y>
<width>621</width>
<height>402</height>
</rect>
</property>
<property name="windowTitle" >
<string>Create Branch</string>
</property>
<layout class="QVBoxLayout" >
<property name="spacing" >
<number>2</number>
</property>
<property name="leftMargin" >
<number>4</number>
</property>
<property name="topMargin" >
<number>4</number>
</property>
<property name="rightMargin" >
<number>4</number>
</property>
<property name="bottomMargin" >
<number>4</number>
</property>
<item>
<layout class="QHBoxLayout" >
<item>
<widget class="QLabel" name="label" >
<property name="text" >
<string>Branch Name</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="local_branch" />
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="branch_groupbox" >
<property name="title" >
<string>Starting Revision</string>
</property>
<layout class="QVBoxLayout" >
<property name="spacing" >
<number>2</number>
</property>
<property name="leftMargin" >
<number>4</number>
</property>
<property name="topMargin" >
<number>4</number>
</property>
<property name="rightMargin" >
<number>4</number>
</property>
<property name="bottomMargin" >
<number>4</number>
</property>
<item>
<layout class="QHBoxLayout" >
<property name="spacing" >
<number>4</number>
</property>
<item>
<widget class="QLabel" name="label_3" >
<property name="text" >
<string>Revision Expression:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="revision" />
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" >
<item>
<widget class="QRadioButton" name="local_radio" >
<property name="text" >
<string>Local Branch</string>
</property>
<property name="checked" >
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="remote_radio" >
<property name="text" >
<string>Tracking Branch</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="tag_radio" >
<property name="text" >
<string>Tag</string>
</property>
</widget>
</item>
<item>
<spacer>
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" >
<size>
<width>201</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QListWidget" name="branch_list" />
</item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" >
<property name="spacing" >
<number>2</number>
</property>
<property name="leftMargin" >
<number>4</number>
</property>
<property name="topMargin" >
<number>4</number>
</property>
<property name="rightMargin" >
<number>4</number>
</property>
<property name="bottomMargin" >
<number>4</number>
</property>
<item>
<widget class="QGroupBox" name="options_groupbox" >
<property name="title" >
<string>Options</string>
</property>
<layout class="QVBoxLayout" >
<property name="spacing" >
<number>2</number>
</property>
<property name="leftMargin" >
<number>4</number>
</property>
<property name="topMargin" >
<number>4</number>
</property>
<property name="rightMargin" >
<number>4</number>
</property>
<property name="bottomMargin" >
<number>4</number>
</property>
<item>
<layout class="QHBoxLayout" >
<item>
<widget class="QLabel" name="label_2" >
<property name="text" >
<string>Update Existing Branch:</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="no_update_radio" >
<property name="text" >
<string>No</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="ffwd_only_radio" >
<property name="text" >
<string>Fast Forward Only</string>
</property>
<property name="checked" >
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="reset_radio" >
<property name="text" >
<string>Reset</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" >
<item>
<layout class="QVBoxLayout" >
<item>
<widget class="QCheckBox" name="fetch_checkbox" >
<property name="text" >
<string>Fetch Tracking Branch</string>
</property>
<property name="checked" >
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkout_checkbox" >
<property name="text" >
<string>Checkout After Creation</string>
</property>
<property name="checked" >
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer>
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" >
<size>
<width>328</width>
<height>46</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" >
<property name="spacing" >
<number>2</number>
</property>
<property name="leftMargin" >
<number>4</number>
</property>
<property name="topMargin" >
<number>4</number>
</property>
<property name="rightMargin" >
<number>4</number>
</property>
<property name="bottomMargin" >
<number>4</number>
</property>
<item>
<widget class="QPushButton" name="create_button" >
<property name="text" >
<string>Create Branch</string>
</property>
<property name="default" >
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="cancel_button" >
<property name="text" >
<string>Cancel</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>cancel_button</sender>
<signal>pressed()</signal>
<receiver>createbranch</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel" >
<x>450</x>
<y>387</y>
</hint>
<hint type="destinationlabel" >
<x>294</x>
<y>209</y>
</hint>
</hints>
</connection>
</connections>
</ui>
+67
View File
@@ -0,0 +1,67 @@
<ui version="4.0" >
<class>items</class>
<widget class="QDialog" name="items" >
<property name="geometry" >
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>73</height>
</rect>
</property>
<layout class="QVBoxLayout" >
<item>
<widget class="QListWidget" name="items_widget" />
</item>
<item>
<widget class="QDialogButtonBox" name="button_box" >
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons" >
<set>QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>items_widget</tabstop>
<tabstop>button_box</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>button_box</sender>
<signal>accepted()</signal>
<receiver>items</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel" >
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel" >
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>button_box</sender>
<signal>rejected()</signal>
<receiver>items</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel" >
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel" >
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>
+155
View File
@@ -0,0 +1,155 @@
<ui version="4.0" >
<class>logger</class>
<widget class="QDialog" name="logger" >
<property name="geometry" >
<rect>
<x>0</x>
<y>0</y>
<width>830</width>
<height>390</height>
</rect>
</property>
<property name="windowTitle" >
<string/>
</property>
<layout class="QGridLayout" >
<property name="leftMargin" >
<number>4</number>
</property>
<property name="topMargin" >
<number>4</number>
</property>
<property name="rightMargin" >
<number>4</number>
</property>
<property name="bottomMargin" >
<number>4</number>
</property>
<property name="horizontalSpacing" >
<number>4</number>
</property>
<property name="verticalSpacing" >
<number>0</number>
</property>
<item row="0" column="0" colspan="4" >
<widget class="QTextEdit" name="output_text" >
<property name="font" >
<font>
<family>Monospace</family>
<pointsize>13</pointsize>
</font>
</property>
<property name="acceptDrops" >
<bool>false</bool>
</property>
<property name="tabChangesFocus" >
<bool>true</bool>
</property>
<property name="undoRedoEnabled" >
<bool>false</bool>
</property>
<property name="readOnly" >
<bool>true</bool>
</property>
<property name="acceptRichText" >
<bool>false</bool>
</property>
<property name="textInteractionFlags" >
<set>Qt::NoTextInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="1" column="0" >
<widget class="QGroupBox" name="groupBox" >
<property name="title" >
<string>Search</string>
</property>
<layout class="QHBoxLayout" >
<property name="spacing" >
<number>4</number>
</property>
<property name="leftMargin" >
<number>8</number>
</property>
<property name="topMargin" >
<number>0</number>
</property>
<property name="rightMargin" >
<number>8</number>
</property>
<property name="bottomMargin" >
<number>4</number>
</property>
<item>
<widget class="QLineEdit" name="search_text" />
</item>
<item>
<widget class="QPushButton" name="next_button" >
<property name="text" >
<string>Next</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="prev_button" >
<property name="text" >
<string>Previous</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="1" >
<spacer>
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" >
<size>
<width>217</width>
<height>24</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="2" >
<widget class="QPushButton" name="clear_button" >
<property name="text" >
<string>Clear</string>
</property>
</widget>
</item>
<item row="1" column="3" >
<widget class="QPushButton" name="close_button" >
<property name="text" >
<string>Close</string>
</property>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>close_button</tabstop>
<tabstop>output_text</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>close_button</sender>
<signal>released()</signal>
<receiver>logger</receiver>
<slot>close()</slot>
<hints>
<hint type="sourcelabel" >
<x>352</x>
<y>277</y>
</hint>
<hint type="destinationlabel" >
<x>199</x>
<y>149</y>
</hint>
</hints>
</connection>
</connections>
</ui>
+1196
View File
File diff suppressed because it is too large Load Diff
+167
View File
@@ -0,0 +1,167 @@
<ui version="4.0" >
<class>merge</class>
<widget class="QDialog" name="merge" >
<property name="geometry" >
<rect>
<x>0</x>
<y>0</y>
<width>673</width>
<height>339</height>
</rect>
</property>
<property name="windowTitle" >
<string>Merge</string>
</property>
<layout class="QVBoxLayout" >
<item>
<widget class="QLabel" name="label" >
<property name="text" >
<string>Merge into %s</string>
</property>
<property name="alignment" >
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox" >
<property name="title" >
<string>Revision To Merge</string>
</property>
<layout class="QGridLayout" >
<property name="leftMargin" >
<number>5</number>
</property>
<property name="topMargin" >
<number>0</number>
</property>
<property name="rightMargin" >
<number>5</number>
</property>
<property name="bottomMargin" >
<number>5</number>
</property>
<property name="horizontalSpacing" >
<number>0</number>
</property>
<property name="verticalSpacing" >
<number>0</number>
</property>
<item row="0" column="0" >
<widget class="QLabel" name="label_2" >
<property name="text" >
<string>Revision Expression:</string>
</property>
</widget>
</item>
<item row="0" column="1" colspan="2" >
<widget class="QLineEdit" name="revision" />
</item>
<item row="1" column="0" >
<widget class="QRadioButton" name="radio_local" >
<property name="text" >
<string>Local Branch</string>
</property>
</widget>
</item>
<item row="1" column="1" >
<widget class="QRadioButton" name="radio_remote" >
<property name="text" >
<string>Tracking Branch</string>
</property>
</widget>
</item>
<item row="1" column="2" >
<widget class="QRadioButton" name="radio_tag" >
<property name="text" >
<string>Tag</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="3" >
<widget class="QListWidget" name="revision_list" >
<property name="alternatingRowColors" >
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" >
<item>
<widget class="QPushButton" name="button_viz" >
<property name="text" >
<string>Visualize</string>
</property>
</widget>
</item>
<item>
<spacer>
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" >
<size>
<width>231</width>
<height>24</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QCheckBox" name="checkbox_squash" >
<property name="text" >
<string>Squash</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkbox_commit" >
<property name="text" >
<string>Commit</string>
</property>
<property name="checked" >
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="button_cancel" >
<property name="text" >
<string>Cancel</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="button_merge" >
<property name="text" >
<string>Merge</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>button_cancel</sender>
<signal>clicked()</signal>
<receiver>merge</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel" >
<x>540</x>
<y>316</y>
</hint>
<hint type="destinationlabel" >
<x>336</x>
<y>169</y>
</hint>
</hints>
</connection>
</connections>
</ui>
+363
View File
@@ -0,0 +1,363 @@
<ui version="4.0" >
<class>options</class>
<widget class="QDialog" name="options" >
<property name="windowModality" >
<enum>Qt::ApplicationModal</enum>
</property>
<property name="geometry" >
<rect>
<x>0</x>
<y>0</y>
<width>654</width>
<height>345</height>
</rect>
</property>
<property name="windowTitle" >
<string>Options</string>
</property>
<layout class="QGridLayout" name="gridLayout_3" >
<item row="0" column="0" >
<layout class="QGridLayout" name="gridLayout_2" >
<item row="0" column="0" >
<widget class="QLabel" name="label_2" >
<property name="text" >
<string>Main Font</string>
</property>
</widget>
</item>
<item row="0" column="1" >
<widget class="QSpinBox" name="global_cola_fontui_size" >
<property name="minimum" >
<number>8</number>
</property>
<property name="value" >
<number>12</number>
</property>
</widget>
</item>
<item row="0" column="2" colspan="3" >
<widget class="QFontComboBox" name="global_cola_fontui" >
<property name="currentFont" >
<font>
<family>Helvetica</family>
</font>
</property>
</widget>
</item>
<item row="1" column="0" >
<widget class="QLabel" name="label_3" >
<property name="text" >
<string>Diff/Console Font</string>
</property>
</widget>
</item>
<item row="1" column="1" >
<widget class="QSpinBox" name="global_cola_fontdiff_size" >
<property name="minimum" >
<number>8</number>
</property>
<property name="value" >
<number>12</number>
</property>
</widget>
</item>
<item row="1" column="2" colspan="3" >
<widget class="QFontComboBox" name="global_cola_fontdiff" >
<property name="fontFilters" >
<set>QFontComboBox::MonospacedFonts</set>
</property>
<property name="currentFont" >
<font>
<family>Bitstream Vera Sans Mono</family>
</font>
</property>
</widget>
</item>
<item row="2" column="0" >
<widget class="QLabel" name="label_13" >
<property name="text" >
<string>Editor</string>
</property>
</widget>
</item>
<item row="2" column="1" >
<widget class="QLineEdit" name="global_gui_editor" >
<property name="text" >
<string/>
</property>
</widget>
</item>
<item row="2" column="2" >
<widget class="QLabel" name="label_11" >
<property name="text" >
<string>History browser</string>
</property>
</widget>
</item>
<item row="2" column="3" colspan="2" >
<widget class="QLineEdit" name="global_gui_historybrowser" >
<property name="text" >
<string/>
</property>
</widget>
</item>
<item row="3" column="0" >
<widget class="QLabel" name="label_12" >
<property name="text" >
<string>Diff Editor</string>
</property>
</widget>
</item>
<item row="3" column="1" >
<widget class="QLineEdit" name="global_gui_diffeditor" >
<property name="text" >
<string/>
</property>
</widget>
</item>
<item row="3" column="2" colspan="2" >
<widget class="QCheckBox" name="global_cola_savewindowsettings" >
<property name="text" >
<string>Remember Window Settings</string>
</property>
</widget>
</item>
<item row="3" column="4" >
<widget class="QCheckBox" name="global_cola_saveatexit" >
<property name="text" >
<string>Save Window Settings At Exit</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0" >
<layout class="QHBoxLayout" name="horizontalLayout_8" >
<item>
<widget class="QGroupBox" name="local_groupbox" >
<property name="title" >
<string>Local Repository</string>
</property>
<layout class="QGridLayout" name="gridLayout" >
<item row="0" column="1" colspan="2" >
<widget class="QLineEdit" name="local_user_name" />
</item>
<item row="1" column="0" >
<widget class="QLabel" name="label_5" >
<property name="text" >
<string>Email Address:</string>
</property>
</widget>
</item>
<item row="1" column="1" colspan="2" >
<widget class="QLineEdit" name="local_user_email" />
</item>
<item row="2" column="0" colspan="2" >
<widget class="QCheckBox" name="local_merge_summary" >
<property name="text" >
<string>Summarize Merge Commits</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="3" >
<widget class="QCheckBox" name="local_merge_diffstat" >
<property name="text" >
<string>Show Diffstat After Merge</string>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2" >
<widget class="QLabel" name="label_6" >
<property name="text" >
<string>Merge Verbosity:</string>
</property>
</widget>
</item>
<item row="4" column="2" >
<widget class="QSpinBox" name="local_merge_verbosity" >
<property name="minimum" >
<number>0</number>
</property>
<property name="maximum" >
<number>5</number>
</property>
<property name="value" >
<number>5</number>
</property>
</widget>
</item>
<item row="5" column="0" colspan="2" >
<widget class="QLabel" name="label" >
<property name="text" >
<string>Number of Diff Context Lines:</string>
</property>
</widget>
</item>
<item row="5" column="2" >
<widget class="QSpinBox" name="local_gui_diffcontext" >
<property name="minimum" >
<number>2</number>
</property>
<property name="value" >
<number>5</number>
</property>
</widget>
</item>
<item row="0" column="0" >
<widget class="QLabel" name="label_4" >
<property name="text" >
<string>User Name:</string>
</property>
</widget>
</item>
</layout>
<zorder>local_user_name</zorder>
<zorder>label_5</zorder>
<zorder>local_user_email</zorder>
<zorder>local_merge_summary</zorder>
<zorder>local_merge_diffstat</zorder>
<zorder>label_6</zorder>
<zorder>local_merge_verbosity</zorder>
<zorder>label</zorder>
<zorder>local_gui_diffcontext</zorder>
<zorder>label_4</zorder>
</widget>
</item>
<item>
<widget class="QGroupBox" name="globalGroupBox" >
<property name="title" >
<string>Global (All Repositories)</string>
</property>
<layout class="QGridLayout" >
<item row="0" column="0" >
<widget class="QLabel" name="label_7" >
<property name="text" >
<string>User Name:</string>
</property>
</widget>
</item>
<item row="0" column="1" colspan="2" >
<widget class="QLineEdit" name="global_user_name" />
</item>
<item row="1" column="0" >
<widget class="QLabel" name="label_8" >
<property name="text" >
<string>Email Address:</string>
</property>
</widget>
</item>
<item row="1" column="1" colspan="2" >
<widget class="QLineEdit" name="global_user_email" />
</item>
<item row="2" column="0" colspan="2" >
<widget class="QCheckBox" name="global_merge_summary" >
<property name="text" >
<string>Summarize Merge Commits</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="3" >
<widget class="QCheckBox" name="global_merge_diffstat" >
<property name="text" >
<string>Show Diffstat After Merge</string>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2" >
<widget class="QLabel" name="label_10" >
<property name="text" >
<string>Merge Verbosity:</string>
</property>
</widget>
</item>
<item row="4" column="2" >
<widget class="QSpinBox" name="global_merge_verbosity" >
<property name="minimum" >
<number>0</number>
</property>
<property name="maximum" >
<number>5</number>
</property>
<property name="value" >
<number>5</number>
</property>
</widget>
</item>
<item row="5" column="0" colspan="2" >
<widget class="QLabel" name="label_9" >
<property name="text" >
<string>Number of Diff Context Lines:</string>
</property>
</widget>
</item>
<item row="5" column="2" >
<widget class="QSpinBox" name="global_gui_diffcontext" >
<property name="minimum" >
<number>2</number>
</property>
<property name="value" >
<number>5</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item row="2" column="0" >
<layout class="QHBoxLayout" name="horizontalLayout_9" >
<item>
<spacer name="horizontalSpacer" >
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0" >
<size>
<width>478</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="save_button" >
<property name="text" >
<string>Save</string>
</property>
<property name="default" >
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="cancel_button" >
<property name="text" >
<string>Cancel</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>cancel_button</sender>
<signal>released()</signal>
<receiver>options</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel" >
<x>563</x>
<y>319</y>
</hint>
<hint type="destinationlabel" >
<x>306</x>
<y>170</y>
</hint>
</hints>
</connection>
</connections>
</ui>
+136
View File
@@ -0,0 +1,136 @@
<ui version="4.0" >
<class>remote</class>
<widget class="QDialog" name="remote" >
<property name="geometry" >
<rect>
<x>0</x>
<y>0</y>
<width>550</width>
<height>512</height>
</rect>
</property>
<property name="windowTitle" >
<string>Push</string>
</property>
<layout class="QVBoxLayout" >
<item>
<layout class="QHBoxLayout" >
<item>
<widget class="QLabel" name="label" >
<property name="text" >
<string>Local Branches</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="local_branch" />
</item>
</layout>
</item>
<item>
<widget class="QListWidget" name="local_branches" />
</item>
<item>
<layout class="QHBoxLayout" >
<item>
<widget class="QLabel" name="label_2" >
<property name="text" >
<string>Remote</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="remotename" />
</item>
</layout>
</item>
<item>
<widget class="QListWidget" name="remotes" />
</item>
<item>
<layout class="QHBoxLayout" >
<item>
<widget class="QLabel" name="label_3" >
<property name="text" >
<string>Destination Branch</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="remote_branch" />
</item>
</layout>
</item>
<item>
<widget class="QListWidget" name="remote_branches" />
</item>
<item>
<layout class="QHBoxLayout" >
<item>
<widget class="QCheckBox" name="ffwd_only_checkbox" >
<property name="text" >
<string>Fast Forward Only</string>
</property>
<property name="checked" >
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="tags_checkbox" >
<property name="text" >
<string>Include tags</string>
</property>
</widget>
</item>
<item>
<spacer>
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" >
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="action_button" >
<property name="text" >
<string>Push</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="cancel_button" >
<property name="text" >
<string>Cancel</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>cancel_button</sender>
<signal>released()</signal>
<receiver>remote</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel" >
<x>502</x>
<y>489</y>
</hint>
<hint type="destinationlabel" >
<x>274</x>
<y>255</y>
</hint>
</hints>
</connection>
</connections>
</ui>
+257
View File
@@ -0,0 +1,257 @@
<ui version="4.0" >
<class>search</class>
<widget class="QDialog" name="search" >
<property name="geometry" >
<rect>
<x>0</x>
<y>0</y>
<width>600</width>
<height>550</height>
</rect>
</property>
<property name="windowTitle" >
<string>Search</string>
</property>
<layout class="QVBoxLayout" >
<item>
<layout class="QGridLayout" >
<item row="0" column="0" colspan="2" >
<widget class="QRadioButton" name="radio_range" >
<property name="text" >
<string>Revision Range Expression</string>
</property>
</widget>
</item>
<item row="0" column="2" >
<widget class="QRadioButton" name="radio_path" >
<property name="text" >
<string>Commits Touching Paths</string>
</property>
</widget>
</item>
<item row="0" column="3" >
<widget class="QRadioButton" name="radio_author" >
<property name="text" >
<string>Author</string>
</property>
</widget>
</item>
<item row="0" column="4" >
<widget class="QRadioButton" name="radio_committer" >
<property name="text" >
<string>Committer</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2" >
<widget class="QRadioButton" name="radio_revision" >
<property name="text" >
<string>Revision ID</string>
</property>
</widget>
</item>
<item row="1" column="2" >
<widget class="QRadioButton" name="radio_message" >
<property name="text" >
<string>Commit Messages</string>
</property>
</widget>
</item>
<item row="1" column="3" colspan="2" >
<widget class="QRadioButton" name="radio_daterange" >
<property name="text" >
<string>Date Range (Start / End)</string>
</property>
</widget>
</item>
<item row="2" column="0" >
<widget class="QLabel" name="maxresults_label" >
<property name="text" >
<string>Max Results</string>
</property>
</widget>
</item>
<item row="2" column="1" >
<widget class="QSpinBox" name="max_results" >
<property name="minimum" >
<number>5</number>
</property>
<property name="maximum" >
<number>9995</number>
</property>
<property name="singleStep" >
<number>5</number>
</property>
<property name="value" >
<number>500</number>
</property>
</widget>
</item>
<item row="2" column="2" >
<widget class="QRadioButton" name="radio_diff" >
<property name="text" >
<string>Diff Content</string>
</property>
</widget>
</item>
<item row="2" column="3" >
<widget class="QDateEdit" name="start_date" >
<property name="currentSection" >
<enum>QDateTimeEdit::DaySection</enum>
</property>
<property name="displayFormat" >
<string>yyyy-MM-dd</string>
</property>
<property name="calendarPopup" >
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="4" >
<widget class="QDateEdit" name="end_date" >
<property name="currentSection" >
<enum>QDateTimeEdit::DaySection</enum>
</property>
<property name="displayFormat" >
<string>yyyy-MM-dd</string>
</property>
<property name="calendarPopup" >
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="0" >
<widget class="QPushButton" name="button_search" >
<property name="text" >
<string>Search</string>
</property>
<property name="shortcut" >
<string>Return</string>
</property>
</widget>
</item>
<item row="3" column="1" colspan="3" >
<widget class="QLineEdit" name="input" />
</item>
<item row="3" column="4" >
<widget class="QPushButton" name="button_browse" >
<property name="text" >
<string>Browse</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QSplitter" name="splitter" >
<property name="orientation" >
<enum>Qt::Vertical</enum>
</property>
<property name="handleWidth" >
<number>2</number>
</property>
<widget class="QListWidget" name="commit_list" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Minimum" hsizetype="Expanding" >
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize" >
<size>
<width>50</width>
<height>50</height>
</size>
</property>
<property name="alternatingRowColors" >
<bool>true</bool>
</property>
<property name="selectionMode" >
<enum>QAbstractItemView::SingleSelection</enum>
</property>
</widget>
<widget class="QTextEdit" name="commit_text" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Expanding" hsizetype="Expanding" >
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="minimumSize" >
<size>
<width>50</width>
<height>150</height>
</size>
</property>
<property name="tabChangesFocus" >
<bool>true</bool>
</property>
<property name="readOnly" >
<bool>true</bool>
</property>
<property name="acceptRichText" >
<bool>false</bool>
</property>
</widget>
</widget>
</item>
<item>
<layout class="QGridLayout" >
<item row="0" column="0" >
<widget class="QPushButton" name="button_export" >
<property name="text" >
<string>Export Patches</string>
</property>
</widget>
</item>
<item row="0" column="1" >
<widget class="QPushButton" name="button_cherrypick" >
<property name="text" >
<string>Cherry Pick</string>
</property>
</widget>
</item>
<item row="0" column="2" >
<spacer>
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" >
<size>
<width>111</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="3" >
<widget class="QPushButton" name="button_close" >
<property name="text" >
<string>Close</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>button_close</sender>
<signal>clicked()</signal>
<receiver>search</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel" >
<x>552</x>
<y>527</y>
</hint>
<hint type="destinationlabel" >
<x>299</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>
+110
View File
@@ -0,0 +1,110 @@
<ui version="4.0" >
<class>stash</class>
<widget class="QDialog" name="stash" >
<property name="geometry" >
<rect>
<x>0</x>
<y>0</y>
<width>683</width>
<height>201</height>
</rect>
</property>
<property name="windowTitle" >
<string>Stash</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout" >
<item>
<widget class="QListWidget" name="stash_list" />
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2" >
<item>
<widget class="QPushButton" name="button_stash_show" >
<property name="toolTip" >
<string>Show the changes recorded in the selected stash stash as a diff</string>
</property>
<property name="text" >
<string>Show</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="button_stash_save" >
<property name="text" >
<string>Save</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="button_stash_apply" >
<property name="toolTip" >
<string>Apply the selected stash</string>
</property>
<property name="text" >
<string>Apply</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout" >
<item>
<widget class="QPushButton" name="button_stash_done" >
<property name="text" >
<string>Done</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="button_stash_drop" >
<property name="toolTip" >
<string>Remove the selected stash</string>
</property>
<property name="text" >
<string>Remove</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="button_stash_clear" >
<property name="toolTip" >
<string>Remove all stashed states</string>
</property>
<property name="text" >
<string>Remove All</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<tabstops>
<tabstop>button_stash_save</tabstop>
<tabstop>button_stash_show</tabstop>
<tabstop>button_stash_apply</tabstop>
<tabstop>button_stash_drop</tabstop>
<tabstop>button_stash_clear</tabstop>
<tabstop>button_stash_done</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>button_stash_done</sender>
<signal>clicked()</signal>
<receiver>stash</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel" >
<x>562</x>
<y>292</y>
</hint>
<hint type="destinationlabel" >
<x>314</x>
<y>157</y>
</hint>
</hints>
</connection>
</connections>
</ui>
-209
View File
@@ -1,209 +0,0 @@
<ui version="4.0" >
<class>ugitCommandDialog</class>
<widget class="QDialog" name="ugitCommandDialog" >
<property name="geometry" >
<rect>
<x>0</x>
<y>0</y>
<width>500</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle" >
<string>Git Command Output</string>
</property>
<layout class="QVBoxLayout" >
<item>
<widget class="QLabel" name="label" >
<property name="text" >
<string>Git Command Output:</string>
</property>
<property name="alignment" >
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QTextEdit" name="commandText" >
<property name="palette" >
<palette>
<active>
<colorrole role="Text" >
<brush brushstyle="SolidPattern" >
<color alpha="255" >
<red>170</red>
<green>170</green>
<blue>127</blue>
</color>
</brush>
</colorrole>
<colorrole role="Base" >
<brush brushstyle="SolidPattern" >
<color alpha="255" >
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="Highlight" >
<brush brushstyle="SolidPattern" >
<color alpha="255" >
<red>0</red>
<green>255</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="HighlightedText" >
<brush brushstyle="SolidPattern" >
<color alpha="255" >
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
</active>
<inactive>
<colorrole role="Text" >
<brush brushstyle="SolidPattern" >
<color alpha="255" >
<red>170</red>
<green>170</green>
<blue>127</blue>
</color>
</brush>
</colorrole>
<colorrole role="Base" >
<brush brushstyle="SolidPattern" >
<color alpha="255" >
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="Highlight" >
<brush brushstyle="SolidPattern" >
<color alpha="255" >
<red>0</red>
<green>255</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="HighlightedText" >
<brush brushstyle="SolidPattern" >
<color alpha="255" >
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
</inactive>
<disabled>
<colorrole role="Text" >
<brush brushstyle="SolidPattern" >
<color alpha="255" >
<red>96</red>
<green>96</green>
<blue>96</blue>
</color>
</brush>
</colorrole>
<colorrole role="Base" >
<brush brushstyle="SolidPattern" >
<color alpha="255" >
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Highlight" >
<brush brushstyle="SolidPattern" >
<color alpha="255" >
<red>68</red>
<green>68</green>
<blue>68</blue>
</color>
</brush>
</colorrole>
<colorrole role="HighlightedText" >
<brush brushstyle="SolidPattern" >
<color alpha="255" >
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
</disabled>
</palette>
</property>
<property name="font" >
<font>
<family>Monospace</family>
<pointsize>13</pointsize>
</font>
</property>
<property name="acceptDrops" >
<bool>false</bool>
</property>
<property name="readOnly" >
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" >
<item>
<spacer>
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" >
<size>
<width>261</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="okButton" >
<property name="text" >
<string>OK</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<tabstops>
<tabstop>okButton</tabstop>
<tabstop>commandText</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>okButton</sender>
<signal>clicked()</signal>
<receiver>ugitCommandDialog</receiver>
<slot>close()</slot>
<hints>
<hint type="sourcelabel" >
<x>352</x>
<y>277</y>
</hint>
<hint type="destinationlabel" >
<x>199</x>
<y>149</y>
</hint>
</hints>
</connection>
</connections>
</ui>
-184
View File
@@ -1,184 +0,0 @@
<ui version="4.0" >
<class>ugitCommitBrowser</class>
<widget class="QDialog" name="ugitCommitBrowser" >
<property name="geometry" >
<rect>
<x>0</x>
<y>0</y>
<width>411</width>
<height>448</height>
</rect>
</property>
<property name="windowTitle" >
<string>Git Commit Browser</string>
</property>
<layout class="QVBoxLayout" >
<item>
<widget class="QSplitter" name="splitter" >
<property name="orientation" >
<enum>Qt::Vertical</enum>
</property>
<property name="handleWidth" >
<number>4</number>
</property>
<widget class="QListWidget" name="commitList" >
<property name="font" >
<font>
<family>Monospace</family>
</font>
</property>
<property name="alternatingRowColors" >
<bool>true</bool>
</property>
<property name="selectionMode" >
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
</widget>
<widget class="QTextEdit" name="commitText" >
<property name="palette" >
<palette>
<active>
<colorrole role="Text" >
<brush brushstyle="SolidPattern" >
<color alpha="255" >
<red>170</red>
<green>170</green>
<blue>127</blue>
</color>
</brush>
</colorrole>
<colorrole role="Base" >
<brush brushstyle="SolidPattern" >
<color alpha="255" >
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
</active>
<inactive>
<colorrole role="Text" >
<brush brushstyle="SolidPattern" >
<color alpha="255" >
<red>170</red>
<green>170</green>
<blue>127</blue>
</color>
</brush>
</colorrole>
<colorrole role="Base" >
<brush brushstyle="SolidPattern" >
<color alpha="255" >
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
</inactive>
<disabled>
<colorrole role="Text" >
<brush brushstyle="SolidPattern" >
<color alpha="255" >
<red>96</red>
<green>96</green>
<blue>96</blue>
</color>
</brush>
</colorrole>
<colorrole role="Base" >
<brush brushstyle="SolidPattern" >
<color alpha="255" >
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
</disabled>
</palette>
</property>
<property name="font" >
<font>
<family>Monospace</family>
<pointsize>12</pointsize>
</font>
</property>
<property name="tabChangesFocus" >
<bool>true</bool>
</property>
<property name="undoRedoEnabled" >
<bool>false</bool>
</property>
<property name="readOnly" >
<bool>true</bool>
</property>
</widget>
</widget>
</item>
<item>
<layout class="QHBoxLayout" >
<item>
<widget class="QLabel" name="label" >
<property name="text" >
<string>Revision Expression</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="revisionLine" />
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox" >
<property name="standardButtons" >
<set>QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>buttonBox</tabstop>
<tabstop>commitList</tabstop>
<tabstop>revisionLine</tabstop>
<tabstop>commitText</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>ugitCommitBrowser</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel" >
<x>205</x>
<y>426</y>
</hint>
<hint type="destinationlabel" >
<x>205</x>
<y>223</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ugitCommitBrowser</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel" >
<x>205</x>
<y>426</y>
</hint>
<hint type="destinationlabel" >
<x>205</x>
<y>223</y>
</hint>
</hints>
</connection>
</connections>
</ui>

Some files were not shown because too many files have changed in this diff Show More