Compare commits

...
2248 Commits
Author SHA1 Message Date
David Aguilar 00670d1ee9 version: Do not write builtin_version.py
Simply update the _default_version variable when creating new
releases instead of having all of the extra code for parsing the
version from git.  This removes a lot of complexity.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-21 03:51:19 -08:00
David Aguilar be8baf91be git-{cola,dag}: Simplify setting up sys.path for the install tree
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-21 03:46:47 -08:00
David Aguilar 7d5842aa82 git-cola 1.7.1
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-21 03:28:49 -08:00
David Aguilar 1cc6bf54b8 doc: Add 1.7.1 release notes
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-21 03:19:51 -08:00
David Aguilar cf2b516cbb widgets.status: Refine selection behavior
The selection behavior changed in 952029b197
to not select items as they travel from staged to unstaged, and
vice versa.  This improved usability but interfered with the
partial staging workflow because partially staged files
were not re-selected.  Similarly, selecting multiple files and
hitting 'rescan' would lose the selection.

Improve these cases by reselecting files from the same
staged/unstaged category when they were previously selected.
This still addresses the usability issue from #97 because
we do not restore selection across the staged/unstaged boundary.

Further improve the interaction by optimizing the selection
behavior for code reviews.  When an unstaged item is staged
(and vice versa), the next item is automatically selected.

Closes #97

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-21 03:02:50 -08:00
David Aguilar 9ebbc67d0c remote: Add space around the checkbox labels
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-21 02:57:31 -08:00
David Aguilar e896f8103f README: Update Python requirement to 2.6
Python 2.6 is old!

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-21 02:34:33 -08:00
David Aguilar d9ac3cfaac Makefile: Add more paths to 'uninstall' target
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-21 02:18:15 -08:00
David Aguilar ca84d144c3 Makefile: Use relative paths in the app.tar.gz
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-20 20:40:18 -08:00
David Aguilar 8fde5f483e ux: Use the default handle width across all splitters
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-20 19:07:08 -08:00
David Aguilar d7ccf2ce8f views.remote: Use the default margin/spacing
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-20 19:03:40 -08:00
David Aguilar 66ea87f0b6 qtutils: Do not pass None to QInputDialog.getText()
The default text should be the empty string.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-20 19:01:29 -08:00
David Aguilar 8da94e29ef defs: Tighten up the default margin/spacing
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-20 19:00:58 -08:00
David Aguilar 72f3c5c237 defs: Use consistent margins/spacing across all of git-cola
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-20 18:58:07 -08:00
David Aguilar 32ed37c780 widgets: Add a module for holding common definitions
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-20 18:51:36 -08:00
David Aguilar 43e116f822 main.view: Fix interactive diff font setting
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-20 17:09:44 -08:00
David Aguilar 8ecff55fe7 version: Simplify the method used to determine git-cola's version
Do not call 'git describe' with the --tags option.
Do not check for worktree 'dirty' state.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-19 22:23:44 -08:00
David Aguilar 303bca3fc5 Makefile: Remove old 'standalone' flag
We removed the need for the --standalone option so remove all
handling for the flag.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-19 22:20:46 -08:00
David Aguilar 8e93dc8d34 errors: Remove unused exceptions
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-18 03:40:06 -08:00
David Aguilar e886380020 git: Simplify process interaction by using communicate()
Guard against deadlocks and other possible errors encountered
during low-memory conditions.  The trace variables were also
consolidated in this simplification pass.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-18 03:39:51 -08:00
David Aguilar 195906459b git: s/load_worktree/set_worktree/
Make the terminology consistent across modules.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-18 03:31:31 -08:00
David Aguilar d33c5ba81a doc: Add mwh to the "Thanks to" file
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-11 17:49:20 -08:00
Michael Homer a8886ffa3d dag: Make library search path symlink-aware
Previously, git-dag would find the directory two up from the running
file and append some path components to it without reference to the
filesystem. When git-cola (and hence __file__) was a symlink this
would break, causing the application to fail to find its own
libraries. Instead, resolve any symlink using os.path.realpath at the
beginning, before any path manipulation.
2011-11-11 17:47:50 -08:00
David Aguilar 88cabc4f81 Merge pull request #98 from mwh/master
Search for libraries in executable needs to be aware of symlinks
2011-11-11 17:42:52 -08:00
Michael Homer b500ac7191 Make library search path symlink-aware
Previously, git-cola would find the directory two up from the running
file and append some path components to it without reference to the
filesystem. When git-cola (and hence __file__) was a symlink this
would break, causing the application to fail to find its own
libraries. Instead, resolve any symlink using os.path.realpath at the
beginning, before any path manipulation.
2011-11-12 12:07:03 +13:00
David Aguilar 952029b197 status: Do not re-select items when staging/unstaging
git-cola attempts to maintain the selection after staging files.
This makes the tree view hard to use because the modified area
keeps losing focus.

Keep the newly-staged item unselected so that the scrollbars do not
change location.  This makes it easier to review and stage many files.

Closes #97

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-10 14:01:50 -08:00
David Aguilar 2af8209ba5 application: Rename cola.desktop to git-cola.desktop
Make it consistent with the name of the project.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-09 17:04:34 -08:00
David Aguilar efb55b2161 applications: Add a git-dag.desktop launcher
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-08 16:55:13 -08:00
David Aguilar d87c0033d2 git-{cola,dag}: Use a better test when detecting the source tree
cola/__init__.py is what we're *really* interested in so check for its
existence instead of blindly checking for a directory called 'cola'.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-08 16:48:08 -08:00
David Aguilar 74f7b49b8a MANIFEST.in: Cleanup old entries and add missing files
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-08 03:32:12 -08:00
David Aguilar 597816c24c MANIFEST.in: Add extras/
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-08 03:25:59 -08:00
David Aguilar 20206bdaf6 packaging: Add a 'COPYING' file
'COPYING' is more common than 'LICENSE' so rename it.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-08 03:25:33 -08:00
David Aguilar cba55bb3b4 packaging: Install the 'git-dag' script
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-08 03:15:09 -08:00
David Aguilar 1c06faab07 packaging: Remove third-party modules
We do not need to ship these now that we depend on either python 2.6's
json module or an external simplejson.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-08 03:14:28 -08:00
David Aguilar f21e55e13a dependencies: Remove all usage of cola.serializer
cola.serializer brings in third-party dependencies that we can do without.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-08 02:54:06 -08:00
David Aguilar 0ae660d5d3 settings: Replace the SettingsManager with the simple Settings class
Replace the bookmarks dialog with a simpler version that does not depend
on QObserver.  Use the simpler Settings class to remove unnecessary
dependencies.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-08 02:22:16 -08:00
David Aguilar 0c2720ff6c widgets.startup: Use the simple Settings object
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-08 01:59:53 -08:00
David Aguilar b1411e74a9 qtutils: Allow callers to provide default text to prompt()
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-08 01:58:38 -08:00
David Aguilar d26fa3c341 settings: Add a standalone Settings class
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-08 01:57:19 -08:00
David Aguilar 835e92fca2 main.view: Remove extra icons from the menus
Only keep icons when the sibling menu items are sufficiently iconized.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-08 01:19:58 -08:00
David Aguilar 23d83f4b5e main.view: Add icons to the menu entries
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-07 03:13:37 -08:00
David Aguilar 310ead0781 qtutils: Add a git_icon() function and use it
Replace hard-coded 'git.svg' paths with a function call.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-07 03:12:24 -08:00
David Aguilar cb7fd86834 tag: Tighten layout and add use a more specific button label
Use the more descriptive 'Create Tag' label instead of just 'Create'.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-07 03:06:42 -08:00
David Aguilar 0367c9680e interaction: Use the 'critical' message where appropriate
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-07 03:05:48 -08:00
David Aguilar 81b4bf50e6 guicmds, qtutils: Add handlers for confirm() and critical()
Handle these global signals so that they can be used from within
editor commands.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-07 03:04:38 -08:00
David Aguilar 49cf77a9c2 qtutils: Do not memoize tr()
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-07 03:02:23 -08:00
David Aguilar 507306d786 qtutils: Make confirm() and question() not need a 'parent' widget
Always use the active window so that callers do not need to
bother passing along a parent widget.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-07 03:02:02 -08:00
David Aguilar 65110c82c1 guicmds: Remove unnecessary 'slot' functions
These functions no longer require a 'parent' widget so the
parent-providing wrappers can be removed.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-07 02:54:20 -08:00
David Aguilar 6a3ddabc88 cfgactions: Do not require a 'parent' widget
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-07 02:50:51 -08:00
David Aguilar 2d35f7c500 stash: Do not require a 'parent' widget
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-07 02:48:18 -08:00
David Aguilar 1cfe7fcdaf stash: Replace 'Remove' with 'Drop'
Use the common "git stash drop" terminology to make things more
intuitive for native git speakers.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-07 02:48:18 -08:00
David Aguilar 6dcfadec92 qtutils: Add a save_as() function and use it
Add a common function for displaying a 'Save As...' dialog.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-07 02:48:18 -08:00
David Aguilar f1e401d6f1 bookmarks: Tighten margin
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-07 02:48:18 -08:00
David Aguilar 43207d4384 widgets.startup: Tighten margins and add icons
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-07 02:48:18 -08:00
David Aguilar 7fc045f2af main, dag: Require 'parent' widgets where appropriate
Rework the widget entry points so that they only require parent
widgets when necessary.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-07 02:48:18 -08:00
David Aguilar 7ee70e99fd qtutils: Remove 'parent' argument to open*_dialog()
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-07 02:34:01 -08:00
David Aguilar c01d689ead app: Remove function for setting up the icon directory
Do the setup inline instead.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-07 02:27:08 -08:00
David Aguilar 6042a86fbd app: Simplify imports by catching PyQt4 import errors early
Avoid the deferred cola imports by importing PyQt4 early on
and exiting when it is not available.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-07 02:25:47 -08:00
David Aguilar 136e9dd82d git-dag: Update copyright notice
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-07 02:23:23 -08:00
David Aguilar 11271f0264 git-cola: Update copyright notice
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-07 02:23:16 -08:00
David Aguilar bedf146a94 classic.view: Adjust imports for consistency
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-07 01:49:31 -08:00
David Aguilar fbbc75bb15 qtutils: Use active_window() everywhere
Replace all QtGui.QApplication.instance().activeWindow() call
sites with qtutils.active_window().

We can later go through and eliminate the 'parent' argument to
many of the callbacks by calling active_window() instead of
passing it along.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-06 01:33:29 -07:00
David Aguilar 552034c966 app: Do not require a parent widget when creating command wrappers
The UI command wrappers originally required a parent widget for use
when creating dialogs, etc.  Use qtutils.active_window() instead so
that the command wrappers can be installed without needing to keep
a reference to the top-level widget.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-05 16:01:43 -07:00
David Aguilar b4469c50bb qtutils: Add active_window() and use it
Replace existing calls to QApplication.instance().activeWindow()
with a call to active_window().

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-05 16:00:31 -07:00
David Aguilar 4b518fa1eb cfgactions: Move the RevisionSelector class to where it is used
The revselect module is only used in one place so remove the extra module.
Simplify the layouts by using QLayout.addStretch() instead of manually
creating spacer items.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-05 15:48:43 -07:00
David Aguilar cc9d9c20bf MANIFEST.in: Add all icons, not just .pngs
git-cola ships .png and .svg icons so use a '*' wildcard
instead of limiting the include to '*.png'.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-05 11:48:58 -07:00
David Aguilar 953796541a guicmds: Replace choose_from_list() with the browse dialog
This eliminates the widgets.listview module and replaces it with
a much nicer file-selection dialog.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-05 11:47:22 -07:00
David Aguilar 042e53a155 widgets.browse: Add a select_file_from_list() method
Build a filesystem tree from a list of file paths and provide a
dialog for choosing a path from the list.  This will replace
the guicmds.choose_from_list() function.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-05 11:46:08 -07:00
David Aguilar f2f5f432e2 widgets.browse: Refactor GitTreeModel into two classes
Create a generic list-based model and build GitTreeModel using it.
GitTreeModel contains the specific details about 'git ls-tree'.

GitFileListModel is able to build a tree structure from a list
of paths.  This feature will be used to replace the ListView class.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-05 11:41:09 -07:00
David Aguilar c3e0cdf5b1 widgets.browse: Factor out the tree widget's model
The "Select File" listview widget will be replaced by a nicer
QStandardItemModel-based dialog which reuses the BrowseDialog.
Factor out its current model so we can plug others in.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-04 23:38:51 -07:00
David Aguilar 0904fd957c status: Add '...' to the 'Revert' actions
The 'Revert' actions present confirmation dialogs so their
menu entries should have a '...' suffix.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-04 18:42:05 -07:00
David Aguilar a7aea04620 status: Add '...' to the 'Delete File(s)' menu action
The 'Delete File(s)' action presents a confirmation dialog
so its menu entries should have a '...' suffix.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-04 18:42:05 -07:00
David Aguilar beb762db4e diff: Improve usability for staging/unstaging/etc.
Refer to the action of 'Undoing changes' as 'Reverting' since
it is easier to understand.

Add beautiful icons to the context menus.

Change the last remaining 'Yes/No' questions to action-centric
confirmation dialogs.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-04 18:41:23 -07:00
David Aguilar 055d881b58 status: Improve usability for staging/unstaging/etc.
Refer to 'Hunks' as 'Sections' since that is more intuitive to
those who are not diff/patch/git experts.

Add beautiful icons to the context menu actions.

Change the last remaining 'Yes/No' question to a more intuitive
action-centric confirmation dialog.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-04 18:29:06 -07:00
David Aguilar c1475614db style: Add Tango SVG icons for common operations
We'll use these to beautify the status and diff widgets.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-04 18:25:35 -07:00
David Aguilar a5133c29ed widgets.browse: Strip off NULL-bytes only
We were calling rstrip() which is a no-op when the bytes we want
to strip are NULL.  Strip NULL explicitly so that we can do
without the extra check at the start of the loop.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-04 15:27:53 -07:00
David Aguilar 568948f48d qtutils.confirm: Add ability to set the default button
Sometimes 'Cancel' should be the default.
Destructive operations, for example, default to 'Cancel'.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-04 15:23:07 -07:00
David Aguilar b42110c874 style: Remove unused stylesheet properties
e593b390f2 removed the --style
option so widgets no longer need to support custom properties
for use by stylesheets.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-04 04:48:20 -07:00
David Aguilar bf2973cd85 branch diff: Support unicode branches and paths
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-04 02:26:59 -07:00
David Aguilar ef0e53bc33 Makefile: Delete builtin_version.py* when cleaning
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-03 23:23:23 -07:00
David Aguilar 025ab85cc0 Makefile: Use a relative path when building the app.tar.gz
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-03 22:51:47 -07:00
David Aguilar d788c6de3e gitcfg: Support bool configuration entries with no value
[test]
		bool

Is considered 'true' by 'git config --bool test.bool'.
These are rare in practice but we should honor them.

Likewise, make sure that we probably handle:

	[test]
		value =

Improve the gitcfg handling for bool values.
Add tests to cover these both of these cases.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-03 19:41:51 -07:00
David Aguilar 6c4a8002ce Makefile: Exclude 'sphinxtogithub' when running tests
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-03 19:36:44 -07:00
David Aguilar df339937f7 git-cola 1.7.0
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-02 00:10:53 -07:00
David Aguilar 6bada56b51 Makefile: Include documentation in git-cola.app
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-02 00:09:37 -07:00
David Aguilar 883a31e6ed main: Use the configured monospace font in the log window
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-01 23:04:09 -07:00
David Aguilar e1be6057b9 dag: Use the brushed-metal style on OS X
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-11-01 22:52:32 -07:00
David Aguilar 8bc836053b dag: Allow passing command-line arguments to git-dag
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-31 15:55:15 -07:00
David Aguilar 365e9eb651 qt: Convert QString to unicode before comparing completion
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-31 15:44:17 -07:00
David Aguilar 839735de76 dag: Update completions when the available refs changes
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-28 04:52:50 -07:00
David Aguilar d4ddc6f907 dag: Close the completion popup when the dialog closes
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-28 04:51:57 -07:00
David Aguilar 65456364c7 views: Improve window titles for git-cola and git-dag
Remove the [brackets] to make the branch name easier to read.
Make the git-dag window title useful at a glance.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-28 04:18:51 -07:00
David Aguilar 8d4974787c dag: Make the widget state persistent
Remember the previously entered 'count' and 'ref' values.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-28 04:01:05 -07:00
David Aguilar 839067e342 views: Use the common state-manipulation functions from qtutils
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-28 04:00:04 -07:00
David Aguilar e5552b5111 qtutils: Add functions for saving and apply saved GUI state
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-28 03:57:31 -07:00
David Aguilar 20c1fc528b qt: Show completion popup when the line edit is clicked
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-28 03:54:12 -07:00
David Aguilar ceb17d1d20 doc: Add ijuma and Blaisorblade to the "Thanks to" file
ijuma suggested issue #33 which added the menu for reusing
previous commit messages in the message editor.

	https://github.com/git-cola/git-cola/issues/33

Blaisorblade pointed out some obvious usability bugs which
inform some of the bigger refactoring and usability
improvements in 1.7.0.

	https://github.com/git-cola/git-cola/issues/89

	https://github.com/git-cola/git-cola/issues/90

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-27 02:05:49 -07:00
David Aguilar 1303633d45 dag: Add a standalone 'git-dag' command
The DAG visualizer can now be invoked by running:

	$ git dag

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-27 01:59:03 -07:00
David Aguilar 23b5c0b0b7 win32: Make the Windows shortcuts say 'git-cola'
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-27 01:24:25 -07:00
David Aguilar baedf8bf14 dag: Add documentation
Add a minimal manpage and html documentation.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-27 01:20:23 -07:00
David Aguilar ea0c0ef83b main: Install the command wrappers in app.main()
The command wrappers will be shared with git-dag so initialize
them in a common routine.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-27 00:53:48 -07:00
David Aguilar 9cfce67e5d app: Ensure sure that the debugging message is delivered
Use a QTimer to force the notification about GIT_COLA_TRACE to
the main GUI thread.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-27 00:50:16 -07:00
David Aguilar 3dff17e2a7 git-cola: Move the environment setup into app.main()
app.main() will be shared with git-dag so consolidate it.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-27 00:50:16 -07:00
David Aguilar 8c888a567c installers: Update project metadata to use the github URL
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-27 00:50:16 -07:00
David Aguilar df3a725d94 cola: Require Qt >= 4.4
All of the conditional checks for QThreadPool can be removed.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-27 00:50:15 -07:00
David Aguilar e593b390f2 usability: Remove the --style option
Things look nice when git-cola follows the system theme.
Remove the unnecessary and seldom-used --style option.

Simplify the icon lookup code so that it uses Qt's resource
directories.  This lets us name icons using, e.g., 'icons:foo.png'
instead of supplying an absolute path.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-27 00:48:10 -07:00
David Aguilar 20707501a1 doc: Add 1.7.0 release notes draft
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-26 20:22:21 -07:00
David Aguilar 1588c43186 gitcmds: Simplify, speedup, and refactor worktree_state()
We originally ran "git diff --exit-code <path>" against every
modified file to detect "partially-staged" files.

Simplify the code so that we only ever call two git commands to
gather staged and modified files.  This eliminates one git call
on startup in the best case and many calls in the worst case
when many files have been modified.

We may later want to split out the diff_upstream(head) call so
that it is invoked by the classic view only since it is not used
anywhere else.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-26 20:20:16 -07:00
David Aguilar 4ddebdbe9d version: Remove unused module
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-26 20:15:26 -07:00
David Aguilar a42c40db09 main.view: Show "exit diff mode" when applying changes from a branch
Also set the button's minimum height so that it is not
drawn flat on OS X.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-26 20:14:14 -07:00
David Aguilar 43b255e0b7 usability: Move 'Rescan' to the middle of the 'actions' widget
The most frequently used items such as 'Stage' and 'Unstage' should be
listed before less-frequently used items such as 'Rescan'.

Closes #95

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-24 15:11:48 -07:00
David Aguilar fcb3231ba0 cmds: Check if .gitignore exists before writing to it
We previously checked whether .gitignore was checked into the repository
before writing to it.  It is faster and more robust to simply check if
the file exists.  This prevents git-cola from overwriting .gitignore
when it has not been added to git.

Closes #94

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-24 15:08:52 -07:00
David Aguilar 28dbff13f8 guicmds: Replace repobrowser with the simpler browse dialog
One less QObserver...

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-22 21:02:06 -07:00
David Aguilar 7499d1c1f6 widgets.browse: Add a static method for selecting a single file
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-22 20:56:23 -07:00
David Aguilar d1e5445d82 dag: Move the blob saving behavior to the browse widget
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-22 20:38:13 -07:00
David Aguilar 9518515adb dag: Register command wrappers when standalone
git-dag will use this soon.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-22 20:08:24 -07:00
David Aguilar d4960d7e29 qt: Update method used for creating autocomplete matches
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-22 15:32:37 -07:00
David Aguilar e3255b6651 dag: Make the auto-completion much more helpful
Highlight the completed text to visually indicate matches.

Change the match style so that it searches the entire entry
for a possible match instead of matching against the beginning
of the string only.

Add icons to show whether the match is a ref, file, or directory.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-22 14:42:11 -07:00
David Aguilar 244d5da838 utils: Add a shell_split() function and consolidate shlex.split() calls
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-22 14:33:32 -07:00
David Aguilar c5a480733f git: Retry git commands when we get ENOMEM
We may be temporarily out of memory and thus unable to start
new sub-processeses.  Detect this condition and retry.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-22 14:31:39 -07:00
David Aguilar 5c92c7af4f qt: Include directory names when auto-completing paths
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-20 16:48:12 -07:00
David Aguilar 8dc4556f95 dag: Improve autocomplete to include files
Improve the interaction with the autocompletion.
Add filenames to the list of possible completions.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-20 16:04:56 -07:00
David Aguilar d1c86ecf10 git-cola.app: Include documentation
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-20 10:38:29 -07:00
David Aguilar e9e5a65fb0 dag: Zoom out a big more by default and adjust margins
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-20 10:34:37 -07:00
David Aguilar 829b7f8c24 archive: Make the dialog wider by default
Allow enough space for a full SHA-1 plus extension.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-18 14:11:33 -07:00
David Aguilar 8e851e582b dag: "Save Tarball" and "Grab File..." operate on a single selected item
Disable these actions when multiple items are selected.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-18 10:30:09 -07:00
David Aguilar 06fa17dc72 browse: Add a key press event to provide better navigation
Add Vim-style key mappings as well as left/right-arrow
navigation shortcuts.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-18 05:10:24 -07:00
David Aguilar d99130aa84 dag: Add 'Grab File...' for getting an older version of a file
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-18 05:02:51 -07:00
David Aguilar 215ae0ef5b archive: Update default filename on startup
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-17 23:05:06 -07:00
David Aguilar 02e7b86582 archive: Make the save button enabled
It was disabled earlier before it knew how to pre-populate itself.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-17 22:58:45 -07:00
David Aguilar 2499192f7c archive: Seed the combobox index from git
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-17 22:50:38 -07:00
David Aguilar ffe1691820 archive: Hand-off focus when toggling the prefix group box
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-17 19:59:36 -07:00
David Aguilar bc1db33b38 qt: Emit a signal when toggling a QCollapsibleGroupBox
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-17 19:58:06 -07:00
David Aguilar 2e574f82ff archive: Make the 'prefix' field operational
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-17 13:19:17 -07:00
David Aguilar 946f9891bc archive: Select 'tar' when 'tar.gz' is not available
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-17 11:45:21 -07:00
David Aguilar 8f8d39e075 widgets: Add an 'Advanced' section to the GitArchiveDialog
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-17 09:39:07 -07:00
David Aguilar a9cc95c1bc dag: Add a 'Save Tarball/Zip' action
Allow exporting of tarballs from the DAG.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-17 04:31:18 -07:00
David Aguilar 4aee59c60c widgets: Add a widget for saving archives
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-17 04:24:10 -07:00
David Aguilar f6c3cd30e2 status: Refactor the tree widget into a class
This makes the context menu overloading simpler since
we simply re-implement events instead of monkey-patching
them from the context of its containing widget.
The end result is simpler and easier to follow.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-15 13:47:28 -07:00
David Aguilar 5fded7d5f8 status: Prompt before deleting files
Prompting should be a preference but for now let's guard
against eager cat-paws.  This is a dangerous feature, after all.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-15 06:51:35 -07:00
David Aguilar cfcb60ea85 merge: Make global commands local
add_global_command() is only used in one place now.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-15 05:59:09 -07:00
David Aguilar ae19f8ca7f stash: Make rescan a local command
Eventually there should be no add_global_command() calls.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-15 05:28:57 -07:00
David Aguilar 66539850bd stash: Make the view use commands
Refactor the view so that it emits signals only.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-15 04:31:18 -07:00
David Aguilar 64a554ecef stash: Make the 'index' checkbox work for 'apply'
There are times with 'git apply --index' fails but
'git apply' does not so allow the checkbox used for 'save'
to also affect the 'apply' operation.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-15 04:31:18 -07:00
David Aguilar f35fa7e288 ctrl: Add method to add a set of commands at once
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-15 04:31:18 -07:00
David Aguilar cde7b1e6ef git: Add "git" convenience variable
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-15 04:31:17 -07:00
David Aguilar 6aaad58f64 sphinxtogithub: Do not error out on older versions of sphinx
Disable sphinxtogithub if sphinx is too old.
Issue a warning when it happens.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-15 04:31:17 -07:00
David Aguilar 7849a83bb1 main.view: Register 'Ctrl+R' for refresh only
Avoid ambiguous shortcut overloads by using 'Ctrl+R' directly
and avoiding the platform-dependent QKeySequence.Refresh.
One some platforms QKeySequence.Refresh != 'Ctrl+R' and the
shortcut is not overloaded there.  On Linux they are equal
which is what creates the overloaded shortcut.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-13 16:42:53 -07:00
David Aguilar 0a429d9098 README: Complete the strange self-referential loop
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-10 23:47:20 -07:00
David Aguilar 49422f5f17 doc: Update homepage URL and thanks section
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-10 23:31:20 -07:00
David Aguilar 494a4cedd6 doc: Use http:// instead of https:// for the homepage
Github does not offer https:// hosting so fix the URLs.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-10 12:32:48 -07:00
David Aguilar a5528a8e1d doc: Use the haiku sphinx theme
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-09 05:40:49 -07:00
David Aguilar 50b5abdccf doc: Do not delete conf.py
This file is no longer generated by the Makefile.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-05 16:59:04 -07:00
David Aguilar 5e416ed523 dag: Avoid mapRectToScene for compatibility with older Qt versions
Do the mapping ourself to allow cola to use older versions of Qt.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-01 14:03:31 -07:00
David Aguilar 607c755d79 Makefile: Remove extra work done in "make dist"
Our tarballs do not need to include the extra "version"
file anymore so remove the unnecessary steps from the dist target.

This target won't really be used anymore since we are just
publishing the repo as-is without any post-processing of the
output of "git archive", but it's still nice to have.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-01 13:20:47 -07:00
David Aguilar 662ef03f9f version: Do not look for a 'version' file anymore
We now keep a default version in the 'version.py' file
so avoid relying on the extra step of creating a 'version'
file at the root of the project.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-01 13:18:05 -07:00
David Aguilar 79dc6a7c79 dag: Allow passing in arbitrary "git log" options
The text input field can now be used like a command-line
whose arguments are passed along to "git log".

This allows us to slice and dice history using the standard
gitcli(1) shell syntax.

The GUI now displays "git log -# [...]" which is familiar and
powerful for git users.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-01 01:27:57 -07:00
David Aguilar 64d5cc74b9 dag: Better support for viewing slices of history
The DAG view accepts arbitrary options such as
"master -- Makefile" which are passed along to "git log".
This means that much of the history can be missing.

Adjust the commit generation numbers according to a simple counter
for commits adjacent to missing history.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-01 01:23:07 -07:00
David Aguilar 824f604814 dag: Guard against division by zero when adjusting scrollbars
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-01 01:23:07 -07:00
David Aguilar 10135bc0c7 main: Remove redundant browse dialog
Less is more.  The DAG viewer is better.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-10-01 00:12:22 -07:00
David Aguilar b0f36b7458 doc: Update 'davvid' links to point to 'git-cola'
Update all the github URLs to point to the new 'git-cola' organization.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-30 09:53:23 -07:00
David Aguilar 2b3dc1f862 INSTALL: Mention brew instead of deprecated packages
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-30 00:52:25 -07:00
David Aguilar 653cb57084 README: Update github links and description
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-30 00:31:57 -07:00
David Aguilar 40cb20bef0 doc: Add sphinxtogithub extension
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-30 00:03:00 -07:00
David Aguilar 9d172acde3 version: Provide a default version
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-29 23:36:52 -07:00
David Aguilar 41b4d618f5 qtcompat: Add compatibility for QtGui.QKeySequence.Preferences
Qt 4.6 does not provide QtGui.QKeySequence.Preferences; add it.
Guard against duplicate shortcut definitions by uniqifying the list.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-28 10:07:28 -07:00
David Aguilar 5a73d32298 dag: Speed up drawing by setting some QGraphicsView rendering flags
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-28 09:49:19 -07:00
David Aguilar 040b5bff6a widgets.commitmsg: Add a menu for loading a previous commit message
Add an item to the context menu for loading previous commit mesages.
Five items are displayed.  Additional message can be selected by
choosing the "More..." menu item.

Closes #33

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-28 09:46:26 -07:00
David Aguilar d0db763fb3 dag.model: Implement __len__()
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-28 09:46:26 -07:00
David Aguilar ca965f9154 widgets.diff: Remove unneeded action
Remove vestigal action from the refactoring.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-28 09:46:26 -07:00
David Aguilar ed276302aa cmds: Add a command for loading previous commit messages
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-28 09:06:19 -07:00
David Aguilar 4d2f283783 main: Use the improved stash dialog
Remove the old stash dialog and stash-related methods from
the main model.  Switch to using the better stash dialog.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-26 22:14:16 -07:00
David Aguilar 335125d2bc stash: Add a simplified and improved stash dialog
The stash dialog now has an inline stash viewer instead of
using the diff viewer from the main UI.  It also uses the
simplified MVC architecture instead of depending on QObserver.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-26 19:54:53 -07:00
David Aguilar 021892b68b stash: Add a cola.stash package
The simpler stash dialog will live here.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-26 19:53:57 -07:00
David Aguilar c291f82054 merge: Remove qtutils usage in the model
Use the notifier instead of calling into Qt directly.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-26 19:53:57 -07:00
David Aguilar a5a7bc702c widgets: Let the diff and commit message editors set the diff font
Handle setting the font during construction.
Factor out the read-only diff viewer into a standalone class.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-26 19:53:57 -07:00
David Aguilar a2cc39ef5b widgets: Move the RevisionSelector to cola.widgets.revselect
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-26 17:33:48 -07:00
David Aguilar 0577622570 widgets: Move the StartupDialog to cola.widgets.startup
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-26 17:28:37 -07:00
David Aguilar b87b81ce5a widgets: Move the CommitMessageEditor to cola.widgets.commitmsg
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-26 17:20:38 -07:00
David Aguilar f8decacb36 widgets.cfgactions: Remove global references
Remove the global "instances" data structure since it is not
needed to prevent Python from garbage collecting the widget.
The widget is kept in scope naturally and thus does not need
us to keep an extra reference.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-26 17:12:20 -07:00
David Aguilar 9be68a937f qt: Add a GitRefLineEdit and use it where appropriate
Add a QLineEdit-derived widget that comes with a GitRefCompleter
built-in.  This lets us avoid repeating the same setup code in
various places.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-26 17:08:28 -07:00
David Aguilar 5b6a369834 qt: Remove observers when GitRefCompleters are deleted
Avoid sending model notifications to dead C++ objects by
removing observers during object deletion.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-26 17:05:59 -07:00
David Aguilar 5eaef7d297 widgets: Fold cola.views.command into cola.widgets.cfgactions
There's no reason for these to be separate modules.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-26 16:52:06 -07:00
David Aguilar e1e68a1279 cleanup: Remove all uses of the 'commands' module
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-26 16:41:06 -07:00
David Aguilar 9fffc28d34 dag: Workaround win32 not supporting NULL in format strings
There must be a bug in msysgit because setting the file
descriptors to binary does not allow NULL in git log's
output when using a %x00 in the format string.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-26 16:18:51 -07:00
David Aguilar 61cf00167d widgets: Move the config action widgets to cola.widgets.cfgactions
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-26 21:00:07 +02:00
David Aguilar d9ce092839 cmds: Properly pass filenames to config actions
There's no need to call commands.mkarg.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-26 21:00:07 +02:00
David Aguilar 16ab711746 widgets: Move the combo dialog to cola.widgets.combodlg
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-26 21:00:07 +02:00
David Aguilar 756d372646 widgets: Move syntax highlighter to cola.widgets.highlighter
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-26 17:39:32 +02:00
David Aguilar ca8fb6e77b widgets: Move the list view to cola.widgets.listview
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-26 17:36:30 +02:00
David Aguilar 43b2655835 widgets: Move the log widget to cola.widgets.log
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-26 17:32:21 +02:00
David Aguilar 4eb5a868cd widgets: Move the about dialog to cola.widgets.about
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-26 17:29:55 +02:00
David Aguilar 58ca064814 widgets: Move the status widget to cola.widgets.status
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-26 17:29:50 +02:00
David Aguilar 43d4a79170 widgets: Move the diff widget to cola.widgets.diff
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-26 17:29:18 +02:00
David Aguilar 17ad34e455 Add a cola.widgets package
Widgets that do not need a complete MVC setup will live here.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-26 17:14:40 +02:00
David Aguilar 0ed0711977 qt: Move the DiffSyntaxHighlighter into the cola.qt module
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-26 17:11:22 +02:00
David Aguilar a18961f51d dag: Remove trailing newline in the commit subject
The list widget items were one row too tall because the text
being shown ended with a newline.  Call rstrip() after
readline() to remove it.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-26 16:10:52 +02:00
David Aguilar 26b26b7059 dag: Make sys.{stdin,stdout,stderr} descriptors binary on win32
The DAG worked around stdout not returning binary data by using
0x01 instead of NULL as a separator in its "git log" format string.

Rework it so that the file descriptors are binary and use NULL in
the format string again.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-26 15:53:00 +02:00
David Aguilar 27fdb43ced status: Remove the platform check when launching mergetool
There is no need to check the platform at runtime now that we
can rely on "git mergetool --no-prompt" being available.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-26 15:33:18 +02:00
David Aguilar 66d5937d23 main: Use the new merge dialog and remove the old one
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-26 15:05:00 +02:00
David Aguilar 5763ce77f0 merge: Add a new merge dialog which uses the MVC framework
This fleshes out the cola.merge package so that it contains the
customary model, view, and controller modules.  This is a drop-in
replacement for old dialog.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-26 15:05:00 +02:00
David Aguilar fd07da80d4 Add a cola.merge package
The merge dialog will live here.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-26 15:04:53 +02:00
David Aguilar 17e91cc027 cmds: Add a VisualizeRevision command
This will be used by the new merge dialog.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-26 15:04:29 +02:00
David Aguilar 96aa745c96 utils: Remove unused functions and fixup style
Remove the unused notify() and debug() functions.
Make the other functions follow PEP-8.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-26 15:04:29 +02:00
David Aguilar 2b8fc80ec2 utils: Remove all remnants of quote_repopath()
This was a workaround for win32 which is no longer needed now that
we use subprocess when backgrounding commands.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-26 15:04:29 +02:00
David Aguilar a4de84fda7 test: Remove pointless test case
We had a test case to prevent circular imports but it's pointless
and gets in the way when refactoring.  It is not needed since we
will simply not introduce any circular imports.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-26 15:04:29 +02:00
David Aguilar cae8704e09 utils: Use subprocess to launch background commands
This removes all of the confusing and problematic win32 hacks
by using the simple Popen(cmd].pid pattern.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-26 15:04:29 +02:00
David Aguilar d3aa1f1b7b app: Register commands in ColaApplication
Avoid needing to duplicate cmds.register() by doing it in the
ColaApplication singleton.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-26 15:04:29 +02:00
David Aguilar f7bbd7e3bc dag: Use the GitRefCompleter from the cola.qt package
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-25 20:34:30 +02:00
David Aguilar 541f061ad9 qt: Add an enhanced GitRefCompelter to the cola.qt package
Use a QStringListModel internally instead of setting the list of
revisions during object construction.  Subscribe to the model's
"updated" message so that changes to the git data is reflected
in the completer.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-25 20:33:59 +02:00
David Aguilar b953490d57 setup.py: Add missing packages
The recent package refactoring forgot to update setup.py.
Update it.

Closes #91

Reported-by: Francisco Ruiz <franciscoruiz@2degreesnetwork.com>
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-19 20:02:41 +02:00
David Aguilar 177c1167df prefs: Switch context using a tab bar
Minimize the number of clicks when editing preferences by using
a tab bar instead of a combo box.

Closes #89

Suggested-by: Paolo G. Giarrusso <Blaisorblade on github>
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-16 09:55:54 +02:00
David Aguilar b0589e11ea classic: Move the classic code into the cola.classic package
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-13 06:01:40 +02:00
David Aguilar 606f135670 doc: Remove API documentation while it is still in flux
Remove the Sphinx API documentation while we refactor cola into
its final shape.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-13 05:54:09 +02:00
David Aguilar 6939cb71af dag: Change button order to Zoom Out / Zoom In
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-13 05:28:35 +02:00
David Aguilar 6b072767f2 dag: Move the DAG code into the cola.dag package
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-12 20:24:16 -07:00
David Aguilar d211d55fbe main: Remove unused signal and alphabetize imports
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-12 16:42:15 -07:00
David Aguilar 03ab7169d4 main: Combine the mainwindow and view modules
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-12 16:32:08 -07:00
David Aguilar eb12e90516 cola: Refactor the main classes out into the cola.main package
The main entry point now lives at cola.app.main() instead of
cola.main.main() to package name conflicts.

This is the first step towards reorganizing the project into
separate packages for each tool/dialog.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-12 16:16:30 -07:00
David Aguilar 573f0e6cbd prefs: Swap the options dialog for the simpler preferences dialog
The new preferences dialog is simpler and does not depend on the
(deprecated) QObserver controller class.

Closes #89

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-12 15:26:47 -07:00
David Aguilar 37e7ef0659 prefs: Use prefs.diff_font() instead of qtutils.set_diff_font()
Centralize all of the preferences for fonts into the prefs module
and use it instead of the qtutils function.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-12 15:20:46 -07:00
David Aguilar 99e6bd2c0b prefs: Add accessors for the diff font and tab width
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-12 15:18:00 -07:00
David Aguilar 226be2e5da prefs.model: Add notification for config changes
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-12 15:17:21 -07:00
David Aguilar 30e7cada55 prefs: Add ability to change font settings
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-12 11:24:53 -07:00
David Aguilar c17187528e prefs: Add a standalone preferences editor
This will replace the QObserver-based Options editor.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-12 05:30:53 -07:00
David Aguilar c4eebda10d views.main: Factor out more widgets from the main window
Make everything notification-based so the main view does not depend
on the QObserver magic.  The diff viewer and commit message editor are
now completely independent widgets that are only loosely coupled to
the main window.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-12 05:30:53 -07:00
David Aguilar 60a888826c qt: Add helpers for creating dockwidgets and menus
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-12 05:30:53 -07:00
David Aguilar c27488d3ff i18n: Use qtutils.tr() instead of i18n.gettext()
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-12 05:30:52 -07:00
David Aguilar a9f9ce2f1d gitcfg: Make the config class observable
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-12 05:30:52 -07:00
David Aguilar f60f03a3c8 views: Use addLayout() instead of addItem() where appropriate
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-12 05:30:52 -07:00
David Aguilar e6b874bfe3 notification: Documentation update and misc. style fixes
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-12 05:30:52 -07:00
David Aguilar 11f1ee5ad5 cmds: Factor out a simpler base class
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-12 05:30:52 -07:00
David Aguilar 46b9617705 cmds: Add signoff and diff-text modifying commands
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-12 05:30:52 -07:00
David Aguilar 5fa239359a cmdfactory: Add support for context-sensitive commands
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-12 05:30:52 -07:00
David Aguilar 0e7de977ea cmdfactory: Fix mistyped 'cmdobj'
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-12 05:30:52 -07:00
David Aguilar efa719451c qtutils: Add helpers for signal routing and widget creation
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-12 05:30:52 -07:00
David Aguilar 0feda99acd cola: Add a controller base class 2011-09-12 05:30:52 -07:00
David Aguilar 3ed3b6e31b qtutils: Add Ctrl+Q as a shortcut for 'Close'
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-10 04:04:04 -07:00
David Aguilar c4cdcdb7b5 difftool: Add a small margin around the file list
Add a margin around the file list used when diffing two
commits.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-10 03:55:56 -07:00
David Aguilar 5381dec8c0 views.main: Make the diff viewer into a standalone widget
Make the main window know nothing about the diff viewer by
moving all of the diff editing functionality into a standalone
DiffTextEdit widget.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-10 03:51:29 -07:00
David Aguilar a098be0814 views.main*: Simplify menus, reduce code, and fix Ctrl+C in diff viewer
Remove the unnecessary 'Edit' menu from the main menu bar.
The 'Options' action moved into the 'File' menu since the
'Edit' menu is empty without Cut/Paste/etc.

Centralize all of the action handling code into views.mainwindow.
Do not register any actions for Copy/Paste/etc. whatsoever so that
the Qt defaults (which do the right thing) are used instead.

This is the simpler thing to do, uses less code, makes a nicer UI,
and fixes being able to use Ctrl+C in the diff viewer.

It also reduces confusion because it is not clear for new users whether
the edit menu operates on the commit message editor or on the diff viewer.

Likewise, leaving the Ctrl+C, etc. keyboard shortcuts alone allows
Qt to use the correct widget-context action (which happens by default).

This is a net reduction of ~100 lines of code. yay :-)

Closes #90

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-09 23:45:19 -07:00
David Aguilar aadc1acd79 dag: Make it easier to see selected commits
Use a darker blue brush to make selected commits stand out.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-09 09:26:36 -07:00
David Aguilar dda8093171 dag: Add 'Cherry Pick' action
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-08 18:37:35 -07:00
David Aguilar fa27e5bdf8 dag: Rename 'notifier' variable for consistency
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-08 18:37:23 -07:00
David Aguilar 479732ce3f dag: Add a 'Create Tag' action
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-08 18:24:37 -07:00
David Aguilar 53f3847529 dag: Consolidate context_menu_event() across both widgets
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-08 18:24:22 -07:00
David Aguilar 8e22859df9 dag: Consolidate update_actions() across both widgets
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-08 18:23:50 -07:00
David Aguilar 95bbba6315 dag: Add 'Create Branch' action to the tree and graph widgets
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-08 17:39:07 -07:00
David Aguilar 0658b1477e dag: Rename commit/item variables for consistency
Refer to scene items as "items" and commit nodes as Commits consistenly.
This makes the terminology consistent between the tree and graph widgets.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-08 16:33:28 -07:00
David Aguilar b4690e4cf8 dag: s/nodecom/notifier/g
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-08 16:21:32 -07:00
David Aguilar 74fc70c16c dag: Guard against empty text in the revision field
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-08 16:15:19 -07:00
David Aguilar 0569081815 dag: Add diff actions to the graph widget
Hook up the right-click actions for diffing commits to the
difftool.diff_commits() helper.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-08 16:08:18 -07:00
David Aguilar b137ac0ae1 dag: Add diff actions to the tree widget
Hook up the right-click actions for diffing commits to the
difftool.diff_commits() helper.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-08 15:57:43 -07:00
David Aguilar c5827f1efd difftool: Add a dialog for diffing commits
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-08 15:52:37 -07:00
David Aguilar 2840343cea dag: Show a diff when starting up
Keep track of commit SHA-1s in the GitDAGWidget so that we broadcast
a selection event to all listeners.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-08 13:00:03 -07:00
David Aguilar b41904baf9 dag: Add 'Create Patch' to the tree widget
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-08 03:38:08 -07:00
David Aguilar 3e9f1613d1 dag: Add stub actions for diffing commits
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-08 03:23:40 -07:00
David Aguilar 00a72f22f7 dag: Synchronize multi-select across tree and graph widgets
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-08 03:21:16 -07:00
David Aguilar aa2006777f dag: Add 'Create Patch' to the graph view
The context menu event can now export patches for selected commits.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-08 01:56:32 -07:00
David Aguilar 0a6288aadc cmds: Guard against an empty list of files in the Diff command
It is possible for a diff command to be issued with an empty
list of files.  Guard against this condition so that we do not
generate a stacktrace and hang.

Closes #88

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-07 11:20:12 -07:00
David Aguilar 4ce1fca204 qtutils: Use QIcon.fromTheme() in theme_icon()
Avoid using GTK by using Qt's built-in QIcon.fromTheme().
Support older versions of Qt by catching AttributeError and
falling back to our built-in icons.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-07 10:29:49 -07:00
David Aguilar 62bd2f5255 dag: Use the zoom in/out icons from the user's theme
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-06 18:46:46 -07:00
David Aguilar a57333116b dag: Use better zoom in/out icons
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-06 18:44:18 -07:00
David Aguilar 4af7a6538a dag: Add completion for branches and tags
Make the revision line edit provides auto-completion for branches and tags.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-05 12:55:29 -07:00
David Aguilar 9fe67894af dag: Display the current branch at startup
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-05 12:54:10 -07:00
David Aguilar 98a5533174 dag: Support unicode branches, tags, and subject lines
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-05 12:44:57 -07:00
David Aguilar a30b627be4 packaging: Depend on git >= v1.6.3
git-difftool originated in git-cola and was later contributed to
the main git project's contrib/ section.  It moved out of contrib/
in git v1.6.3.

git-cola also uses "git mergetool --no-prompt" and "git diff --patience"
which both appeared in v1.6.2.

Remove our version of difftool and depend on git >= v1.6.3 so that
we do not have to maintain these scripts anymore.  Remove the runtime
checks for "mergetool --no-prompt" and "diff --patience" now that the
entire project depends on a modern git.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-04 19:25:47 -07:00
David Aguilar cedb6e461e dag: Add GUI controls for zoom in/out
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-04 19:05:54 -07:00
David Aguilar 5e0ebbba3d dag: Retain scrollbar position when zooming in/out
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-04 19:05:26 -07:00
David Aguilar c232364b2a dag: Hook up GUI controls for displaying history
The "max results" spinbox and "revision" line edit now control
the displayed history.  The "Display" button clears the graph
and starts a new query.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-04 19:04:47 -07:00
David Aguilar f5a08c6e6b dag: Support unicode in log messages and author names
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-04 18:36:40 -07:00
David Aguilar d81efa06e5 dag: Synchronize selection between graph and tree widgets
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-03 19:53:00 -07:00
David Aguilar 9c8b495d98 dag: Fit view to the selected node on startup
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-03 02:11:51 -07:00
David Aguilar f951a3d306 dag: Make the graph take half of the screen only
Uri was right (as usual).

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-03 02:05:49 -07:00
David Aguilar aebec48195 dag: Resize tree when the main splitter moves
I forgot to update this when we reworked the layout.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-03 01:52:06 -07:00
David Aguilar 1a82ee4db3 dag: Make the layout vertical
Place the controls on the left and the graph on the right.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-02 14:48:43 -07:00
David Aguilar f6fff51d7d dag: Adjust column sizes whenever the log treewidget is resized
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-02 00:08:32 -07:00
David Aguilar e33bd3cc15 dag: Adjust widget sizes in the show() callback
This makes sure things look good when standalone and when called from
git-cola's Tools menu.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-02 00:00:24 -07:00
David Aguilar 7671ad28dd dag: Show merge details in the diff widget
Determine whether or not to include merge information when displaying
a commit in the diff widget.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-01 23:48:25 -07:00
David Aguilar 0e90c26e2e dag: Do not show node in the diff view when rubber-band selecting
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-01 23:46:42 -07:00
David Aguilar 5ca21751e0 dag: Add a list view for the log output
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-09-01 23:46:07 -07:00
David Aguilar 0388df418c dag: Restore ability to select nodes using the left mouse button
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-08-26 19:22:41 -07:00
David Aguilar ecaf80a17a dag: Add a context menu
This will eventually do operations on the selection.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-08-26 09:27:24 -07:00
David Aguilar 06f8519e19 dag: Micro-optimize paint methods
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-08-26 09:26:38 -07:00
David Aguilar 18c6d7ff69 dag: Rework background thread to process more commits at once
We were sending commits from the reader thread once per commit.
This floods the main GUI with signals.  Throttle it back by
processing commits in larger chunks.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-08-26 09:23:58 -07:00
David Aguilar 01de62f578 dag: Make labels into a separate item class
The label drawing code was originally part of the Node item class.
This had the negative side-effect that clicking on the label would
select the node even when the node contained no labels.

Use a separate, non-selectable, QGraphicsItem subclass for labels.
Make it a child of the node so that it can be positioned relative
to the node's position in the scene graph.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-08-25 20:55:05 -07:00
David Aguilar 28c696380a dag: Use a pointing-hand cursor for commits
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-08-25 20:55:02 -07:00
David Aguilar a187d3a8dd dag: Make nodes taller
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-08-25 03:37:22 -07:00
David Aguilar 45ce3b4a09 version: Only run git describe when .git exists
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-08-25 03:22:17 -07:00
David Aguilar 7e721943c0 views.dag: Add navigation shortcuts for "J", "Shift+J", "K", and "Shift+K"
J and K navigate through a node's relatives.  These shortcuts match vim.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-08-24 04:38:48 -07:00
David Aguilar 71b8d795d2 views.dag: Use QActions instead of overriding keyPressEvent()
Attach shortcuts using QActions instead of playing keyPressEvent games.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-08-24 03:35:10 -07:00
David Aguilar 2d3ce6be44 views.dag: Adjust padding around scene rect
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-08-24 03:32:50 -07:00
David Aguilar 7efb6ea23d views.dag: Select HEAD on startup
Move the "commit selected" callback into itemChange().
Set the 'HEAD' node selected when starting up.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-08-24 03:26:12 -07:00
David Aguilar cc45442862 views.dag: Set splitter heights to 2/3 graph, 1/3 controls
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-08-24 03:23:10 -07:00
David Aguilar 4824c48b8d views.dag: Add 'thirdparty' to sys.path when running standalone
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-08-24 03:22:27 -07:00
David Aguilar ba60e451d9 qtutils: Add an add_action() helper for creating QActions
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-08-24 03:20:09 -07:00
David Aguilar c918b77fd7 views.dag: Use generation numbers when drawing the DAG
Use prettier colors when drawing the DAG.

Adjust item sizes to make things fit together more closely.

Use class attributes to avoid creating creating colors in every
instance of the Node class.

Use a simple calculation based on a commit's generation number
to determine its (x, y) location in the scene.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-08-23 13:56:00 -07:00
David Aguilar 2f30c346b3 models.dag: Keep track of the generation number for commits
The generation number is a robust and simple property we can use
when drawing the DAG.  Change the "git log" command so that it
lists commits in reverse-topological order.  This allows us to
track generation numbers while building the graph incrementally.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-08-23 13:56:00 -07:00
David Aguilar 5d4d43dcff views.dag: Do not keep references to GitDAGWidgets
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-08-23 13:56:00 -07:00
David Aguilar 3e96c6db2b views.dag: Tighten up the DAG widget's layout
The splitter widgets are now collapsible.
The DAG graphics view's default size is now larger.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-08-23 13:56:00 -07:00
David Aguilar 120b4edf0f views.main: Pass a parent widget to the DAG view
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-08-23 13:56:00 -07:00
David Aguilar d0388c6e65 setup.py: Try git when printing the release version
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-08-20 19:50:40 -07:00
David Aguilar 39302a13ec doc: Add 1.4.3.5 release notes
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-08-20 19:40:56 -07:00
David Aguilar e281c208ab Makefile: Cleanup stray files in $prefix/lib*
Sometimes python's LIBDIR is not the same as the lib/ directory used by
distutils so change the Makefile so that it uses lib*.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-08-19 12:25:54 -07:00
David Aguilar 46f7530225 doc: Add Karl B. to the "Thanks To" page
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-08-16 20:31:00 -07:00
David Aguilar b367a90a94 Merge pull request #87 from kbielefe/inotify-enhancements
Inotify enhancements
2011-08-16 20:29:05 -07:00
Karl Bielefeldt a9356a41a3 Add Windows support for inotify events 2011-08-16 13:10:13 -05:00
Karl Bielefeldt dfa8841fd8 Use filenames to speed up inotify rescans 2011-08-16 13:06:16 -05:00
David Aguilar 3a459d32e3 models.gitrepo: Add a task manager class to avoid PyQt segfaults
The task manager holds references to individual tasks to prevent Python's
garbage collector from reaping running tasks, which causes a segfault.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-08-09 15:58:14 -07:00
David Aguilar 996189613f models.gitrepo: Add some micro-optimizations
GitRepoModel._create_dir_entry() gets called many times for large repos so avoid
expensive "." lookups where possible.

GitEntryManager.entry() now uses a local reference to its instance
dictionary to avoid doing extra Python name lookups.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-08-09 15:57:54 -07:00
David Aguilar 039b4b2ed6 models.gitrepo: Fix typo calling the wrong base class in GitRepoModel.__init__
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-08-09 15:56:58 -07:00
David Aguilar ca9dfb8e3e remote: Improve user interaction for remote actions
Make sure the 'remote' section is always at the top and focused.
When doing 'pull', do not select a local branch by default.

This ensures that we do "git pull $remote" instead of
"git pull $remote $branch", which only updates the local
branch and leaves the remote tracking branch behind.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-08-09 15:56:42 -07:00
David Aguilar 844f5c1350 views.main: Improve the user interaction when committing with no changes
Change the "Yes" / "Cancel" confirmation buttons to say
"Stage and Commit" / "Cancel", which is clearer and more user-friendly.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-08-09 15:55:53 -07:00
David Aguilar 79cf462dbe Makefile: Rework variables for better readability
Variables that are externally defined should use ALL_CAPS.
Internal variables should use small_caps.

The net effect is that command names and DESTDIR are the
only capitalized variables, which is simple and consistent.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-08-08 03:24:27 -07:00
David Aguilar d8525e47b9 cola.version: Remove regular expressions
Replace usage of the 're' module with simpler but equivalent
string checks.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-08-08 03:17:31 -07:00
David Aguilar dbfd93a5c3 Makefile: Better support for out-of-git builds
The top-level Makefile depended on the existence of cola's
git repository to generate its version number.

Rework the Makefile so that the "make dist"-generated "version"
file is used instead.  This is done by scraping the output of
"python cola/version.py", which knows to look for the "version"
file before trying to use "git describe".

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-08-07 22:45:19 -07:00
David Aguilar 1cd1672276 style: Remove unused imports and other fixes
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-08-01 10:38:40 -07:00
David Aguilar 44fc1dbf29 models.main: Tighten-up the required params for config_set()
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-07-30 02:12:57 -07:00
David Aguilar d4be2558ba tests: Update settings test case to remove module global
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-07-30 02:11:04 -07:00
David Aguilar 22bdc9d1aa models.main: Remove unused vars
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-07-30 01:49:45 -07:00
David Aguilar 6da5dc666f Makefile: Update 'tags' target to use find and xargs
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-07-29 17:31:02 -07:00
David Aguilar 6b40eed965 cmds: Log stderr when applying partial diffs
stderr was not getting captured which meant that output from 'git apply'
was going to the shell.  Capture stderr and log it instead.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-07-29 16:34:50 -07:00
David Aguilar 3d14ec1a36 remote: Use the phrase "Transferring" instead of "Connecting"
"Connecting" makes users think that we're trying to
establish a connection.  We're really transferring data
to/from the remote so use that verb instead.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-07-15 17:07:05 -07:00
David Aguilar 3bbc82dd6e settings: Do not 'import user'
The use of import user in settings.py causes anything in a user's
PYTHONSTARTUP script to execute.  This can have unexpected side effects.

Use os.path.expanduser('~') instead of user.home.

Closes #86

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-07-15 17:02:03 -07:00
David Aguilar c4d05efdfe dag: Avoid embedding NULL in git output
Newer version of msysgit do not allow NULL in git's output so
use byte 0x01 as our separator instead.

Closes #85

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-07-01 02:43:37 -07:00
David Aguilar cff51b2ff2 classic: Avoid embedding NULL in git output
Newer version of msysgit do not allow NULL in git's output so
use byte 0x01 as our separator instead.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-07-01 02:42:58 -07:00
David Aguilar 9ebdfd942f gitcmds: Use --pretty=format:... for older gits
Older versions of git do not understand --format=...
so use --pretty=format:... instead.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-07-01 02:13:17 -07:00
David Aguilar 9218e16272 cmds: Handle unicode in the UntrackedSummary command
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-06-30 20:17:33 -07:00
David Aguilar 7ff1bbe966 qtutils: Use selectedItems() instead of currentRow()
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-06-30 20:12:33 -07:00
David Aguilar 3f136a496b push: Honor remote.$remote.pushurl
"git push" uses remote.$remote.pushurl when configured so follow
the same rules when displaying the remotes.

Closes #83

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-06-30 20:04:12 -07:00
David Aguilar 3d6e63b403 status: Anchor new .gitignore entries to the root
Adding "foo" to .gitignore should use "/" to anchor it to the root.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-06-30 03:25:38 -07:00
David Aguilar 134c70f756 Add Audrius to the thanks page
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-06-30 03:22:49 -07:00
David Aguilar 7e7abcd058 Merge pull request #84 from shivas/issue59
.gitignore: Adds additional right-click command for untracked files to add them to .gitignore
2011-06-30 03:09:29 -07:00
Audrius Karabanovas df904f0a18 .gitignore: Adds additional right-click command for untracked files to add them to .gitignore file in repository
If .gitignore file isn't used in repository, it's created as new file and needs to be added to repository manualy

solution for issue #59 at https://github.com/davvid/git-cola/issues/59
2011-06-29 23:06:41 +04:00
David Aguilar 246060e74f perf: Avoid calling 'git update-index --refresh' too often
We now call 'git update-index --refresh' on startup and in the 'Refresh'
command only.

The other commands call model.update_file_status() which avoids
updating the entire index, updating remotes, etc.

This shaves off a few unneeded git calls.  The stage/unstage methods
previously included some performance hacks to avoid needing to rescan
for changes.  These have been removed in favor of the simpler "just rescan"
logic.  This will become less of an issue in the future when we do all
rescanning in a background thread.

An important note is that all of the editor commands call update_status()
explicitly instead of relying on the base class to call it for them.
This is better since it makes things more explicit.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-06-18 04:39:48 -07:00
David Aguilar 43aacbc260 gitcmds: Fix unstaging of files when amending a commit
Pass an explicit head when unstaging files with 'git reset'.
This makes unstaging files from the previous commit work correctly
since we do not always want to reset to the contents of 'HEAD'.

The net effect is that we now call 'git reset HEAD -- <paths>' for the
normal case and 'git reset HEAD~ -- <paths>' when amending a commit.

Closes #82

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-06-18 03:45:22 -07:00
David Aguilar 37b5ed08ef remote: Show the right remote name when push fails
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-06-01 14:33:26 -07:00
David Aguilar 3b936753fc stash: Add an icon to the 'Close' button
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-05-27 13:43:27 -07:00
David Aguilar 746dcea928 stash: Parent the dialog so that it is centered
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-05-27 13:43:27 -07:00
David Aguilar 62c60587e6 remote: Rename 'Cancel' to 'Close' and add icons to the buttons
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-05-27 13:43:27 -07:00
David Aguilar eb98e69a8c remote: Parent the information dialog so that it is centered
Add a 'parent' flag to qtutils.information() so that we can
parent the message box to the specified parent.
This makes the dialog appear centered relative to the parent.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-05-27 13:42:34 -07:00
David Aguilar 094b3c5279 remote: Display a completion dialog at the end of fetch/push/pull
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-05-27 01:50:39 -07:00
David Aguilar c85dc03e72 views.remote: Use a better default size
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-05-27 01:35:54 -07:00
David Aguilar eb3a1adb50 qtutils: Make information() prettier and more user-friendly
This adds the ability to provide informative and details text in the
information() helper.  A better icon is used as well.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-05-27 01:33:38 -07:00
David Aguilar da8eb59f66 status: Make the tree animated and use uniform row heights for performance
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-05-24 03:33:08 -07:00
David Aguilar 63fb34ce4f mainwindow: Make 'Signoff' and 'Commit' tool buttons with icons
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-05-24 03:19:34 -07:00
David Aguilar 81eb60dd97 mainwindow: Center the position label
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-05-24 03:19:34 -07:00
David Aguilar 727c8df183 views.stash: Use qt.create_toolbutton() instead of duplicating effort
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-05-24 03:19:34 -07:00
David Aguilar 9b07159568 qt; Add create_toolbutton() and make create_button() consistent
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-05-24 03:19:34 -07:00
David Aguilar 3faaa3da0e mainwindow: Remove 'Search' menu
The 'Search' menu actions moved into Actions->Grep and Commit->Search.
This eliminates an entire menu, which is good.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-05-24 02:50:38 -07:00
David Aguilar 7379783ec7 search: Add better support for paths with spaces, etc.
Use the symmetrical subprocess.list2cmdline() and shlex.split()
to better support paths with spaces.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-05-24 02:49:02 -07:00
David Aguilar 1de012f8b7 merge: Use qtutils.confirm() when asking whether to abort a merge
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-05-24 02:08:21 -07:00
David Aguilar 538a3b1583 qtutils: Translate all strings in confirm()
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-05-24 02:08:21 -07:00
David Aguilar 6c3913aaef i18n: Set text domain and setup environ in i18n.install()
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-05-24 01:30:08 -07:00
David Aguilar 010440a47c qtutils: Add close_icon() and more docstrings
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-05-23 20:26:55 -07:00
David Aguilar aadbc8541a qtutils: Add icons for Save, Apply, Discard, and Ok
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-05-23 09:39:47 -07:00
David Aguilar 7b557b2fa2 stash: Rework the entire stash interaction to be more intuitive
We now disable unavailable actions, have a much nicer UI,
and provide prompts with more intuitive interfaces.

Death to the old stash dialog!

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-05-22 23:53:29 -07:00
David Aguilar a723a81079 qtutils: Add a 'confirm' function for better user interrogation
question() asks Yes/No questions which are not very user-friendly.
Dialog buttons should say things like 'Save', not 'Yes'.

confirm() allows the caller to supply the text and icon for the
confirmation button so that the interaction can be improved.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-05-22 23:52:05 -07:00
David Aguilar 59f9a4639a qtutils: Adjust whitespace
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-05-22 23:37:34 -07:00
David Aguilar 2455bb831a qtutils: Make selected_item() operate on the selection, not the curren item
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-05-22 23:37:06 -07:00
David Aguilar 23832e6c5a models.main: Add ability to parse the 'name' portion of a stash
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-05-22 23:35:32 -07:00
David Aguilar 6324036319 qobserver: Register clicked() signal for buttons instead of release()
Registering on release() does not allow us to click, hold, and drag away
to prevent the action from triggering.  clicked() is better because what
we really want to know is whether we were clicked, not whether the
release event arrived.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-05-22 22:11:51 -07:00
David Aguilar fdbce9b00d stash: Rescan after applying and saving stashes
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-05-22 21:12:41 -07:00
David Aguilar b08e125c87 stash: Use a nicer layout in the stash GUI
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-05-22 08:44:45 -07:00
David Aguilar dca01509c5 mainwidnow: Simplify the commit message editor layouts
Remove an unnecessary layout and use consistent spacing around the buttons.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-05-22 08:33:54 -07:00
David Aguilar e373d27e75 status: Make top-level items hidden by default
The status widget is constructed before its state is set so sometimes
cola may appear with its four top-level "Staged", "Unstaged",
"Unmerged", and "Untracked" items in an empty state before being
immediatley updated.

Avoid this by hiding the top-level items on first construction so that
they only appear later when the widget is populated.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-05-22 08:21:57 -07:00
David Aguilar 33fdd38b11 status: Remove the global status instance
Remove the global singleton since we don't really need it.
Create a normal instance in the main window instead.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-05-22 08:14:46 -07:00
David Aguilar 352767fa3a remote: Fix animation on Mac OS X by enabling the progress dialog
Disabling the parent widget disables the progress dialog on Mac OS X.
Work around this by explicitly enabling the progress dialog
after disabling the parent dialog.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-05-20 03:59:32 -07:00
David Aguilar 5ab9d12f90 remote: Use a background thread when calling git fetch/push/pull
This fixes the progress bar behavior on Linux by using a QRunnable
to handle the heavy lifting.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-05-20 04:16:17 -07:00
David Aguilar 5d8dcf9e18 mainwindow: Tighten the layouts for diff and log widgets
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-05-20 03:05:51 -07:00
David Aguilar de751a993e ux: Make the 'Merge' dialog modal
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-05-20 00:31:04 -07:00
David Aguilar c667500d30 ux: Make the 'Create Tag' dialog modal
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-05-20 00:30:33 -07:00
David Aguilar ad6e151d48 stash: Make the dialog modal and use a simpler layout
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-05-20 00:30:09 -07:00
David Aguilar 450b92715b Update copyright for 2011
Heh.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-05-20 00:13:17 -07:00
David Aguilar 1bc259730f doc: Add 1.4.3.4 release notes
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-05-20 00:12:26 -07:00
David Aguilar 35291f00eb doc: Add "Thank to Christian Jann and Justin Lecher"
Christian really helped round out our unicode handling
Justin fixed cloning in the --prompt dialog.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-05-20 00:10:22 -07:00
David Aguilar baad1d4496 remote: Disable the dialog and show a progress bar in fetch/push/pull
This makes the interaction much nicer by giving feedback during the
remote fetch, push, and pull operations.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-05-19 23:57:20 -07:00
David Aguilar 1580666545 rmeote: Make the 'remote' dialogs modal to the current window
This makes the remote dialogs drop down in the context of the
main window.  It also adds a cool animation.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-05-19 23:56:13 -07:00
David Aguilar c19f2fdd4e ux: Remove syntax highlighting from the 'Command Output' window
The syntax highlighting was not very useful and could sometimes make
things harder to read.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-05-19 22:47:56 -07:00
David Aguilar 8124f27e19 remote: Show an error message when 'Push' fails
"git push" might fail but we provide no indication of this to
the user.  Show a helpful error message when this happens.

Closes #69

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-05-19 22:43:23 -07:00
David Aguilar f849eee3c7 gitcmds: Split lines after unicode decoding
We were decoding the output of splitlines which is completely wrong.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-05-19 22:43:23 -07:00
David Aguilar 62359adc34 mainwindow: Tighten the message editor layout
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-04-29 01:25:35 -07:00
David Aguilar e1175c3761 mainwindow: Replace spacers with addStretch() calls
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-04-29 01:18:42 -07:00
David Aguilar 066fd281db ux: Rename "Options" to "Preferences"
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-04-29 01:15:37 -07:00
David Aguilar 2012d6c8d3 Merge branch 'jl/clone-with-prompt'
* jl/clone-with-prompt:
  Fix for issue 76, Clone doesn't work with --prompt.
2011-04-26 01:29:38 -07:00
David Aguilar 7101cb8303 gitcmds: Support unicode in all_refs()
This allows us to have unicode branches and tags.

Closes #79

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-04-26 01:27:03 -07:00
David Aguilar 560ae38f68 views.combo: Handle unicode content
Porting cola to python3k will probably require a cola.compat
variable where we set unicode = str.  Or not.  It'd be nice
to support both 2 and 3 with the same codebase, but maybe
that's crazy.  :-/

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-04-26 01:27:03 -07:00
David Aguilar 75e7315cb8 gitcmds: Handle unicode in for_each_basename()
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-04-26 01:27:03 -07:00
David Aguilar 2fbccecc0d gitcmds: Handle unicode in current_branch()
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-04-26 01:27:03 -07:00
David Aguilar 3361761bc3 views.main: Properly decode the path to the merge message
The path to .git/MERGE_MSG may contain unicode characters so decode
it before trying to read the file.

Closes #74

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-04-23 14:22:15 -07:00
David Aguilar 1645870bb4 gitcmds: Support unicode in repository paths for the "git init" case
We were not properly handling repositories with unicode in
their paths for the "git init" case.  We read .git/HEAD directly
and thus need to decode the path to .git/HEAD since its parent
directory may contain unicode characters.

Closes #74

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-04-23 12:11:13 -07:00
David Aguilar ada59fa952 doc: Document the --prompt option and fix a typo.
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-04-23 00:21:14 -07:00
David Aguilar 2989422aee app: Use four-space indents in instance()
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-04-23 00:20:48 -07:00
David Aguilar 8c1d1c8f05 doc: Add 1.4.3.3 release notes
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-04-22 20:21:20 -07:00
David Aguilar 7cf0246a14 startup: Fix the 'clone' button in the dialog
We removed the Qt 'parent' field so stop providing it in
the startup dialog.

Fixes: #76

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-04-22 20:12:44 -07:00
Justin Lecher 623b809b8a Fix for issue 76, Clone doesn't work with --prompt.
Signed-off-by: Justin Lecher <jlec@gentoo.org>
2011-04-16 11:45:12 +02:00
David Aguilar 9b666a5e03 main: Add a '--prompt' option and use it in the desktop launchers
git-cola assumes that the current directory is a git repository.
This is less helpful when launching git-cola through a launcher icon
because it may find a git repository in $HOME.

Add a '--prompt' flag and use it in all of the launchers.
This forces git-cola to prompt the user for a repository before
launching the main GUI.

Closes #75

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-04-15 02:11:01 -07:00
David Aguilar 1db197ca4d bookmarks: Pass '--repo' when launching git-cola on a bookmark
Otherwise git-cola launches in-place when opening a bookmark.

Closes #75

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-04-15 01:58:51 -07:00
David Aguilar 73a7191758 startup: Handle repository paths with unicode characters
Closes #74

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-04-15 01:40:07 -07:00
David Aguilar 00f6b77ee1 settings: Handle home directories with unicode characters
Closes #74

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-04-15 01:38:46 -07:00
David Aguilar bc5940bd36 guicmds: Handle cloning repos that contain unicode characters
Closes #74

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-04-15 01:38:00 -07:00
David Aguilar 6a09349645 bookmarks: Handle home directories with unicode characters
Closes #74

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-04-15 01:37:14 -07:00
David Aguilar 297ad386e3 INSTALL: Fix mistyped win32 installer path
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-04-13 02:38:57 -07:00
David Aguilar 920f736b6e doc: Add 1.4.3.2 release notes draft
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-04-10 03:59:38 -07:00
David Aguilar 3ee70d6800 gitcfg: Properly decode unicode in file paths
Both the user's home directory and the repository directory can
contain unicode characters in their path.  Decode inputs from
the filesystem so that these paths are accepted.

Closes #74

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-04-10 03:43:10 -07:00
David Aguilar 2975050c94 utils: Remove unused 'commands' import
We needed 'commands' back when we built our own strings using
'commands.mkarg()'.  We now use subprocess.list2cmdline() instead.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-03-19 20:06:56 -07:00
David Aguilar a2e46a327f utils: Fix launching background commands on windows
Newer versions of msysgit no longer require the win32_expandpaths()
hack to expand paths after the double-dash.

Remove this old hack to make newer gits happy.

This basically means we don't support the older msysgit which
required the workaround, which is fine.  If we find someone is
still using an old version we can always add a check later.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-03-19 19:30:48 -07:00
David Aguilar e823d670cc utils: Use subprocess.list2cmdline() to launch commands in the background
This is probably safer then relying on commands.mkarg().

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-03-19 19:17:38 -07:00
David Aguilar bd7f59bc05 cmds: Use sys.executable instead of 'python'
Do not require 'python' in the $PATH by using sys.executable.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-03-19 19:15:14 -07:00
David Aguilar 55a71ee4ab views.main: Hold onto background tasks to avoid segfaults on older PyQt versions
PyQt 4.6.2 segfaults if we don't hold onto a reference to our QRunnable tasks.
Python assumes they are unreferenced and safe to garbage collect.

Keep a reference to the task objects to prevent Python from garbage
collecting them.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-03-10 16:14:33 -08:00
David Aguilar cec9bbaef0 main: Read initial git state in a background thread
Use QThreadPool to offload several git operations so that
git-cola starts up faster.  We can later look into using
'git status --porcelain' to eliminate a few more git calls.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-03-10 00:38:55 -08:00
David Aguilar ab29466607 views.main: Load GUI state in a background thread
Every little bit helps when it comes to startup time.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-03-10 00:38:55 -08:00
David Aguilar 36fece9243 views.main: Load config actions in a background thread
Loading configured actions can be expensive so do it in the background.
The actions module now returns config action names instead of doing
the menu updating itself.  When QThreadPool is available we listen
for ''install_config_actions' and add callbacks for each name
sent to it.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-03-10 00:38:55 -08:00
David Aguilar e15237e62b views: Make observer callbacks thread-safe
Prepare to offload expensive operations onto a background thread
by making all of the observer-registered GUI callbacks thread-safe.
We do this by using Qt's signal/slots.  When a callback happens in
another thread it simply posts a signal using emit().

The main (GUI) thread registers for these signals and handles
updating the views in response to the signals.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-03-10 00:38:55 -08:00
David Aguilar 2ae4777638 gitcfg: Micro-optimize the stat cache
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-03-10 00:38:55 -08:00
David Aguilar ee150af446 difftool: Add support for p4merge
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-03-10 00:38:55 -08:00
David Aguilar d32d2bea41 difftool: Use read-only mode when using *vimdiff
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-03-10 00:38:55 -08:00
David Aguilar f54b479f47 difftool: Use "compare" to launch Araxis merge
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-03-10 00:38:55 -08:00
David Aguilar 106cddcef6 difftool: Specialize diff options for emerge and ecmerge
The ecmerge documentation mentions the following form:

	ecmerge --mode=diff2 $1 $2

Since git-difftool is about diffing, we should use that instead
of --mode=merge2.  Likewise, this drops the $MERGED argument to
emerge, as discussed on the git list ($gmane/117930).

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-03-10 00:38:55 -08:00
David Aguilar 5260e0f067 tests: Fix the gitcmds test case
Reset the cache where appropriate.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-03-10 00:38:55 -08:00
David Aguilar 5af01cdbbf doc: Add 1.4.3.1 release notes
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-02-27 00:02:01 -08:00
David Aguilar 792148e069 doc: Add "Kevin K" to the "Thanks to" page
He's provided the Fedora translations patch which
was applied in 1aaeccd369.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-02-27 00:01:40 -08:00
David Aguilar f9bb2c1407 cmds: Use 'UsageError' in the commit template command objects
Add error checking so that we can detect when users provide either
bad paths or unconfigured commit message templates.

Previously, an unconfigured "commit.template" git config variable
would result in a stacktrace.  We now present users with a helpful
error message instead.

Closes #72

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-02-26 23:38:26 -08:00
David Aguilar 68e464c8f0 cmdfactory: Catch 'UsageErrors' and provide useful feedback
Prompt users when a 'UsageError' occurs.  We currently use the simple
'information' callback.  Different error classes can define fancier
ones later as needed.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-02-26 23:30:25 -08:00
David Aguilar 814227f984 errors: Add 'UsageError' for use by command objects
UsageError represents usage errors.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-02-26 23:29:01 -08:00
David Aguilar 84e1922b4d inotify: Rework thread timeouts to consume less resources
Register known paths up front to avoid checks within a loop.
Updates timeouts so that we sleep for slightly longer.
We no longer use quit() when ending the inotify thread.
We call stop() and wait for it to complete.  This allows us
to do without the setTerminationEnabled(True) call,
which is generally discouraged.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-02-02 02:24:20 -08:00
David Aguilar 107b336826 inotify: Watch for IN_CLOSE_WRITE instead of IN_CREATE
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-02-02 02:24:20 -08:00
David Aguilar 61cc4575a6 inotify: Do not hide the 'Rescan' button when inotify is enabled
Some people find it useful.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-02-02 02:24:20 -08:00
David Aguilar 8fb8a27fa0 views.main: Always call keyPressEvent() in the diff view
Ensure that we don't interfere with Qt's event processing by
unconditionally calling the default keyPressEvent() in our
custom callback.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-02-02 02:23:08 -08:00
David Aguilar 091ffcd496 views.about: Decrease font size and reformat "about" text
Avoid wrapping lines by reflowing the copyright paragraph.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-01-24 18:30:30 -08:00
David Aguilar 986e205e76 classic: Redo startup initialization for standalone mode
The gitrepo QStandardItemModel checks fields in the model which were not
initialized in the 'git cola --classic' standalone case.
Scan the repository before initializing any GUIs so that all of the
model data is available beforehand.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-01-20 03:19:19 -08:00
David Aguilar d6f9095b98 Makefile: Rename Darwin targets to make more sense
Users might want to 'make git-cola.app' and have it DTRT instead of
giving them a tarball.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-01-20 03:00:06 -08:00
David Aguilar b1dad205d7 classic: Rework the status column to use Uri Okrent's snazzy sigil icons
QStandardItem only lets us set a single icon which is good enough for now.
The 'Untracked' text was made less cluttered by simply showing a '?' instead.
That way only the statuses with icons have text next to them.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-01-20 02:52:25 -08:00
David Aguilar 44b816381a classic: Add icons for staged, modified, etc.
Funky fresh icons for the classic view courtesy of Uri Okrent.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-01-20 02:52:18 -08:00
David Aguilar 1736682a77 Revert "models: Handle adding .gitignore'd files"
This reverts commit 16e5126461.

When staging an upper-level folder using 'cola classic'
in ends up staging all the untracked files in that subdirectory.
Oops!  Adding ignored files is an edge case and we don't need
to support it if it hurts the common case.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-01-20 02:49:10 -08:00
David Aguilar d393307727 Makefile: Separate Darwin app bundle and tarball targets
This lets us "make app-bundle" to get an untarred git-cola.app.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-01-20 01:38:08 -08:00
David Aguilar dbb89e54fe gitcfg: Fix 'git config' caching
Re-initialize the data dictionaries directly instead of
calling reset().  This fixes git config caching since
the cache data isn't continually discarded.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-01-15 09:03:38 -08:00
David Aguilar 18c14746dc view.main: Simplify 'Signoff' and 'Commit' callbacks
Remove the unnecessary editor command for the 'signoff' action
so that the Qt widget can do the heavy lifting.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-01-15 07:44:12 -08:00
David Aguilar 1aaeccd369 setup.py: Fix installation of translations per Fedora
Incorporate Fedora's fix for the translations path.
This originally appeared in cola-1.4.3-translations.patch.

Patched-by: Kevin Kofler <Kevin@tigcc.ticalc.org>
Signed-off-by: David Aguilar <davvid@gmail.com>
2011-01-12 19:26:43 -08:00
David Aguilar 16e5126461 models: Handle adding .gitignore'd files
If a file is .gitignore'd we are unable to add it because we are using
'git add' as-is.  Pass '--force' so that we can stage files regardless
of whether they exist in .gitignore or not.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-01-12 19:13:25 -08:00
David Aguilar 6aa01795e0 models: Handle staging an arbitrary number of files
Slice the arguments passed on the command line so that staging a large
list of files does not error out with 'errno: Argument list too long'.

We do this by incrementally staging 42 files at a time.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-01-12 19:12:36 -08:00
David Aguilar 18100eb677 win32/cola: Unify style and guard against spaces in Python paths
Users might have Python installed in a path with a space in it
so guard against this possibility even if we don't check any
of those paths ourselves.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-01-02 18:17:03 -08:00
David Aguilar a75b9e111d views.startup: Raise the widget only after showing it
Qt on OS X doesn't honor raise_() unless show() has been
called so swap the order.

Signed-off-by: David Aguilar <davvid@gmail.com>
2011-01-02 18:05:32 -08:00
David Aguilar 05364663e4 views.mainwindow: Make the 'Cola Classic' tool dockable
This adds the ability to have a dockable 'Cola Classic' tool.
This is enabled by setting the 'cola.classicdockable' configuration.  e.g.:

	git config cola.classicdockable true

The default is 'false' simply because it slows the down startup time
and is especially noticable on large repositories.

Closes #56

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-12-28 03:02:58 -08:00
David Aguilar 5dc0868487 controllers.classic: Micro-optimize expanding folders
Avoid looking up the 'entry' function each time by looking it up once
outside of the loop body.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-12-28 02:57:31 -08:00
David Aguilar 5905459466 models.commit: Remove call to popen.kill()
That function doesn't even exist.  This is in error-handling that
only happens when you forcibly kill cola while loading the dag view.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-12-28 02:30:11 -08:00
David Aguilar ee38c44863 cola.settings: Guard against malformed json
If for whatever reason ~/.cola contains bad json cola won't startup.
Act as if the file didn't exist instead.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-12-28 02:00:01 -08:00
David Aguilar cd5c1bb039 darwin: Add git-osx-installer $PATH
Add common /usr/local/bin and /usr/local/git/bin to $PATH
in the OS X wrapper script.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-12-27 19:34:31 -08:00
David Aguilar cdc2011134 Makefile: Generate git-cola-$(version).app.tar.gz
This makes it consistent with the Windows installer.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-12-27 19:13:40 -08:00
David Aguilar 5e19e3ecb1 doc: Add 1.4.3 release notes
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-12-27 18:56:47 -08:00
David Aguilar ec271b19bb darwin: Remove dependency on py2app for OS X deployment
We now ship separate installers for Qt/PyQt so we don't need to
ship them inside of git-cola.app anymore.  This cuts down on the
OS X download size tremendously and allows us to ship a single
git-cola.app for all OS X platforms.

The only difference between platforms are the Qt/PyQt libraries
which are available as a separate download.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-12-27 15:07:38 -08:00
David Aguilar 9683fa918b darwin: Raise cola only after showing the main widget
Qt on OS X doesn't honor raise_() call unless show() has been
called so swap the order.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-12-27 07:50:13 -08:00
David Aguilar 50edfcf994 gitcmds,qtutils: Guard against unicode file paths
Paths with unicode are not supposed to hit the "else:" block in eval_path()
but guard against it anyways by decoding inputs and encoding outputs.

Closes #64

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-12-06 23:16:19 -08:00
David Aguilar 61ae73fc2e gitcmds: "globals to locals" micro-optimization
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-12-06 23:00:33 -08:00
David Aguilar e67e142e8f gitcmds: Honor gui.diffcontext by using the right configuration variable
cola mistakenly used "diff.context" when it meant "gui.diffcontext"
per git-config(1).

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-12-06 22:59:12 -08:00
David Aguilar 121f0aee66 gitcmds: Centralize diff options to guard against diff.external
Refactor usage of 'git diff' to pass '--no-ext-diff'.
Avoid launching user-configured diff.external viewers by updating
the _common_diff_opts() function and ensuring that all git.diff
callers use it.

Closes #67

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-12-06 22:56:21 -08:00
David Aguilar 29a5e71745 dag: Automatically size widget relative to the desktop size
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-11-01 00:17:33 -07:00
David Aguilar 09e851a987 test: Fix test_currentbranch()
Clear the stat cache during test setup so that there
is no test cross-pollination.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-10-24 23:29:12 -07:00
David Aguilar 3a064b687f test/tmp: Add .gitignore so that this directory always exists
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-10-24 23:19:35 -07:00
David Aguilar 85a4908e96 test/helper.py: Remove debug print message
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-10-24 23:17:04 -07:00
David Aguilar 3f88b33d9b gitcfg: Fix gitcmds test by clearing the cache key during reset()
Ensure that we re-read the configs after reset() by clearing the
stat cache.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-10-24 23:15:14 -07:00
David Aguilar edbab74c55 test: Remove unnecessary module functions
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-10-24 23:14:14 -07:00
David Aguilar 8906ada850 test: Fix the gitcfg test case
Avoid globals in the helper testlib so that every test
gets its own temp repository created by tempfile.mkdtemp().

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-10-24 22:52:12 -07:00
David Aguilar c1a6023566 dag: Add a useful widget to show the currently selected commit
When a node is clicked we copy display commit information and
copy its SHA-1 into the copy/paste buffer.  This can be expanded
to have more functionality such as cherry-pick, format-patch, branch,
merge, rebase, etc.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-10-23 18:56:17 -07:00
David Aguilar c06f7dc755 gitcmds: Add 'diff_info()' to provide text with a custom header and diff
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-10-23 18:56:17 -07:00
David Aguilar 6b6c0c310c views.mainwindow: Remove unused import
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-10-23 18:56:17 -07:00
David Aguilar 6f80a9f4a8 dag: Set default zoom to 2x
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-10-23 18:56:17 -07:00
David Aguilar c4becc0683 dag: Limit SHA-1 display to 8 characters
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-10-23 18:56:17 -07:00
David Aguilar 60c679a23f views.dag: Add a control area to the dag widget
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-10-23 18:56:17 -07:00
David Aguilar 6bf3db4449 Revert "setup.py: do not replace shebang lines in python scripts"
This reverts commit 544032b2d6.

Arch's default 'python' is python3k so prepare for this by
re-enabling distutil's shebang line replacement.

This allows packagers to supply the $(PYTHON) variable
to 'make' and have it do the right thing.  e.g.:

	make PYTHON=python2 prefix=/usr install

This causes the installed 'git-cola' script to have have
the correct shebang line.

Closes #66

Reported-by: alyst via github.com
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-10-22 15:53:13 -07:00
David Aguilar 0383e34bdf doc: Final 1.4.2.5 release notes
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-10-20 01:30:20 -07:00
David Aguilar 8a022389c1 doc: Add initial 1.4.2.5 release notes
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-10-20 01:22:30 -07:00
David Aguilar 5173faed40 Makefile: Remove hard-coded lib/ path
Ask python for the default LIBDIR and use that to calculate the
path to the leftover eggs produced by distutils.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-10-20 01:21:20 -07:00
David Aguilar 10e7da10c9 views.status: Call the wrapped mouse release callback in the wrapper
We wrap the status view's QTreeWidget mouse release event to clear the
selection when clicking in an empty area.  We were not calling the
wrapped objecct's original event handler which caused odd behavior
in newer versions of PyQt.  Call into the original handler to get
the correct behavior.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-10-20 01:03:40 -07:00
David Aguilar dd6bb1acb2 qt: Check layouts against None instead of assuming bool(layout) is True
PyQt 4.7.3 no longer evaluates bool(layout) to True.
Thanks to the patient Ubuntu and Arch users who reported this problem.

Closes #62

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-10-20 00:25:11 -07:00
David Aguilar cb382e4357 views.status: Copy selected path to clipboard when clicked
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-08-29 19:28:09 -07:00
David Aguilar fb294f3a9b views.main: Make Ctrl+C "copy to clipboard" in the diff viewer
Closes #63

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-08-25 01:09:08 -07:00
David Aguilar b14ff6c261 doc: Add 1.4.2.4 release notes
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-08-24 17:50:13 -07:00
David Aguilar 2bbe2c2537 doc: Update references to 'cola.gitcmd'
'cola.gitcmd' got merged into 'cola.git'.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-08-17 15:45:08 -07:00
David Aguilar dada04c548 status: Remove "single click to (un)stage"
Uri recommended we get rid of this.
It's confusing and disorienting to have different behavior
based on where you click on an item.

Signed-off-by: David Aguilar <davvid@gmail.com>
Signed-off-by: Uri Okrent <uokrent@gmail.com>
2010-08-17 15:42:01 -07:00
David Aguilar 7154f5e16c actions: Add ability to create annotated tags
cola now creates annotated tags when a tag message is
supplied.  Previously, cola allowed creating tag messages
creating signed tags only.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-08-17 10:32:02 -07:00
David Aguilar 35ea2cefdf QFlowLayoutWidget: Hold a reference to the layout
This ensures that Python won't try to garbage-collect the layout.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-08-02 12:14:13 -07:00
David Aguilar a5756bf09b Revert "mainwindow: Simplify the 'Actions' widget"
This reverts commit 73acd20d23.

I was reading bug #62 incorrectly.
They were talking about the actions _dialog_,
not the actions widget ;-)

Conflicts:

	cola/views/mainwindow.py

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-08-02 01:01:14 -07:00
David Aguilar 760a6a72cd cmds: Remove reference to non-existent utils.shell_quote()
utils.shell_quote() was removed in favor of commands.mkarg().

Closes #62

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-08-02 00:58:25 -07:00
David Aguilar c1dc4181bf mainwindow: Hold a reference to the actions widget's layout
This shouldn't be needed but it may work around a bug in
PyQt4 4.7.4.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-08-02 00:54:04 -07:00
David Aguilar 0276f1b000 doc: Add 1.4.2.3 release notes
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-07-31 07:29:07 -07:00
David Aguilar 73acd20d23 mainwindow: Simplify the 'Actions' widget
PyQt4 4.7.4 loses its bearings when a widget modifies the
direction of its QBoxLayout in a resize event.

We depended on this behavior to allow the actions widget to
automatically switch between vertical and horizontal layouts.

Simplify the code by just sticking to a vertical layout.
All of the actions on the 'Actions' widget are also
available in the menus these days.

Closes #62

Reported-by: alyst via github.com
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-07-31 05:27:47 -07:00
David Aguilar 901a8a2a57 usability: Allow un/staging all by right-clicking on top-level items
We can now stage or unstage all files by right-clicking on the
'Staged' or 'Unstaged' icons.

Closes #57

Suggested-by: Shish Moom <webmaster@shishnet.org> via github
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-07-27 04:24:32 -07:00
David Aguilar 995877a653 unicode: Encode paths passed to slurp() and write()
Closes #61

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-07-27 03:27:52 -07:00
David Aguilar 2bdae67221 usability: Allow staging all files when committing
Staging files one at a time is a pain; normally from the
command line I just do 'commit -a'.

A GUI equivalent would be nice. Instead of 'Commit' coming
back with 'There are no staged files die', it could say,

'There are no staged files.
 Do you want to stage and commit all modified files?'

Closes #55

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-07-24 12:42:11 -07:00
David Aguilar 802e0811dd mainwindow: Add 'Fetch', 'Push', and 'Pull' to the actions menu
Having these available in the 'Actions' menu would be useful
and more intuitive.  This also allows us to save some screen
real-estate since we no longer need the 'Actions' widget to
access these commands.

Closes #58

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-07-24 12:19:01 -07:00
David Aguilar 1243ab61a2 i18n: Unconditionally install a gettext translator
We were only calling i18n.install() when 'locale' was passed to
the ColaApplication constructor.  We should call it unconditionally
otherwise we won't be able to translate anything.

Additionally, call i18n.gettext() in create_button() so that
the action buttons are translated too.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-07-24 11:56:53 -07:00
David Aguilar 1cc942976f mainwindow: Make action button labels consistent
'Push...' should have three trailing dots.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-07-24 11:52:39 -07:00
David Aguilar e5b35c1af5 darwin: Add command classes from 'extra' when building git-cola.app
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-07-22 14:42:00 -07:00
David Aguilar aadd26c00a Update release notes for 1.4.2.2
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-07-22 14:11:54 -07:00
David Aguilar c4355b28e2 gitcfg: Use os.path.join() to construct the path to ~/.gitconfig
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-07-08 01:08:16 -07:00
David Aguilar 7e970792a4 ssh-askpass: Do not hang on exit
ssh-askpass's window sometimes freezes after the
password is typed.  Each exit causes the window
to be destroyed again, resulting in a hang.

The destroy event might come from the window manager
so rebind <Destroy> inside of its handler before exiting.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-07-07 01:08:34 -07:00
David Aguilar 34762da4fa git: Replace utils.shell_quote() usage with commands.mkarg()
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-06-17 14:07:10 -07:00
David Aguilar 27b0eadcde portability: Move cola.commands to cola.cmds
Otherwise we can't "import commands" from the Python stdlib.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-06-17 14:07:10 -07:00
David Aguilar effbfb6270 git: Fold cola.gitcmd into cola.git
These two are very closely related so they might as well
live in the same module.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-06-17 14:07:10 -07:00
David Aguilar 171594a61a doc: Add 1.4.2.2 release notes
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-06-17 14:06:28 -07:00
David Aguilar b53e69823f views.dag: Add a visual indicator for merge commits
They're slightly darker.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-06-17 14:05:49 -07:00
David Aguilar 87d0848d38 views.dag: Improve the graph layout algorithm
This algorithm is slightly more appealing because it bends chains
of commits that lead to parallel branches.

It's not perfect, but it's one of the simpler things we can do
with the streaming one-node-at-a-time approach in use.

We could probably make a very nice layout by post-processing
the graph using a thread but it's not *that* bad, either.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-06-17 14:05:21 -07:00
David Aguilar 203d59b115 views.dag: Use the parent/child attributes from Commit objects
We don't need to track this in the view now that the commit objects
provide them.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-06-17 14:02:53 -07:00
David Aguilar 19bdbed35a views.dag: Make mouseWheel panning faster
Increase the scale factor so that panning with the mouse wheel
translates more.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-06-17 14:01:24 -07:00
David Aguilar da991ab186 views.dag: Decrease the minimum zoom factor
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-06-17 14:00:25 -07:00
David Aguilar a6a73211cc views.dag: Remove unneeded itemChange() callback
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-06-17 13:59:33 -07:00
David Aguilar 6beea81452 views.dag: Remove calls to Edge.adjust() in Node.add_edge()
There's no reason to do this in add_edge() since the information
is incomplete on the first call.  We now handle calling adjust()
outside of add_edge(), so this call is unnecessary.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-06-17 13:59:07 -07:00
David Aguilar 186b1f15f2 views.dag: Do not set the Z value in Node.paint()
There's no reason to do this if we're not going to allow moving nodes.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-06-17 13:57:43 -07:00
David Aguilar 3d57a83450 views.dag: Micro-optimize Node type(), shape(), boundingRect(), and glyph()
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-06-17 13:57:07 -07:00
David Aguilar ea8ab2dc11 views.dag: Micro-optimize Edge.type()
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-06-17 13:55:59 -07:00
David Aguilar ae51a72acb views.dag: Remove 'graph' from the Node constructor
This was unused.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-06-17 13:55:32 -07:00
David Aguilar c46c1a81b9 models.commit: CommitFactory for creating Commit objects
This allows us to lazy-load parent commits.
The advantage is that we now have parent commit objects that
are lazy-loaded with thier metadata.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-06-17 13:53:47 -07:00
David Aguilar 980d874889 models.commit: Remove unused 'commits()' function
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-06-17 11:37:23 -07:00
Michael Geddes 96f4423b31 Fix staging lines/hunks in win32.
Patches are required to be without CRLF endings
so open/write files in binary mode.

Closes #53

Signed-off-by: Michael Geddes <michael@frog.wheelycreek.net>
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-06-15 21:24:57 -07:00
David Aguilar 15b3d95ca6 qtutils: Do not memoize create_treewidget_item()
It causes problems in the status tree widget.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-06-14 20:28:19 -07:00
David Aguilar 5d00b99974 decorators: Replace 'FunctionMaker' with a simple function
We don't need any of the function signature-mangling features
so let's just rewrite this code.  This is also faster since it
no longer needs the 're' and 'inspect' modules.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-06-14 15:41:33 -07:00
David Aguilar 3dce4c9732 views.dag: memoize() expensive graph drawing methods
shape() and boundingRect() were heavy hitters when profiling the
'git dag' using cProfile.  Use memoize() to optimize away their
expensive calculations.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-06-08 14:11:51 -07:00
David Aguilar 6c47625e08 qtutils: memoize() icon creation and gettext translations
Eliminate more local caches in favor of the memoize() solution.
This makes the code easier to read and simpler in implementation.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-06-08 14:07:20 -07:00
David Aguilar 2ea82700aa qtutils: Ensure two newlines between each function definition
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-06-08 14:03:45 -07:00
David Aguilar 0259c4ca0a utils: memoize() platform introspection functions
We probably don't gain too much by memoizing is_win32() but it
probably doesn't hurt to shave off an instruction or two either.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-06-08 14:01:20 -07:00
David Aguilar ac21b569d3 decorators: Use memoize() to implement all factory functions
This eliminates the global variables used to implement
singleton factories throughout cola.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-06-08 13:59:27 -07:00
David Aguilar 754acf8a4b version: Use memoize() to simplify and speedup version comparisons
This eliminates all of the module globals by using memoize().
The code is simpler too since it does not have to handle caching.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-06-08 13:55:12 -07:00
David Aguilar 271fd9a199 Add cola.decorators to provide the memoize() decorator
http://en.wikipedia.org/wiki/Memoization

We're including code from python-decorator for ease of installation.
Our addition is the memoize() decorator.  memoize() will allow us to
simplify many of our singleton factory methods.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-06-08 13:52:47 -07:00
David Aguilar 904b126eae mainwindow: Add '...' to fetch, push, pull, and stash buttons
These launch a separate dialog so use '...' to provide a visual indication.

Closes #51

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-06-02 14:56:38 -07:00
Uri Okrent 89f0036448 Cola only needs pyqt4 >= 4.1
Signed-off-by: Uri Okrent <uokrent@disneyanimation.com>
2010-05-26 18:11:22 -07:00
David Aguilar b381361430 doc: Add 1.4.2.1 release notes
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-05-19 00:34:44 -07:00
David Aguilar 1d028ba3cc commands: Don't expandvars() the command since 'sh -c' doess it for us
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-05-08 02:11:03 -07:00
David Aguilar 864af552bb commands.RunConfigAction: Fix uninitialized 'title' variable
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-05-05 22:00:26 -07:00
Michael Geddes e691073611 Use 'sh -c' for menu commands
git-gui uses 'sh -c $cmd' when implementing menu commands.
Passing shell=True works fine on Linux, but on Windows
it goes through cmd.exe, which is not the shell we want
to use.

Always use 'sh -c' so that behavior is consistent
across platforms.

Signed-off-by: Michael Geddes <michael@frog.wheelycreek.net>
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-05-05 21:55:33 -07:00
David Aguilar aba3869285 perf: Optimize away a few more calls to 'git' when unstaging
The unstaging operation no longer calls any of the following:

	git update-index --refresh
	git diff-index --cached HEAD
	git diff-index HEAD

We now simply unstage the file using 'git reset' and then ask
git for the list of all files using 'git ls-files -z'.

We can then classify each unstaged path as being either
modified or untracked based solely on whether or not each
path is a member of the set of known files.

This should noticibly speed things up when working with large
repositories.

Closes #48

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-05-05 15:32:57 -07:00
David Aguilar 378a08c165 perf: Optimize Unstage command to minimize git calls
This shaves a few more git calls from the unstage command
which makes things more pleasant everywhere, and on Windows
in particular.

Closes #48

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-05-04 01:57:54 -07:00
David Aguilar bb108320c7 models.main: Optimize stage_paths() to avoid git calls
This fixes the performance issues when staging paths.
Fixing the performance of the Unstage command will require a
different strategy.  Dulwich and similar libraries are longer-term
things to consider.

Closes #48

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-05-04 01:11:18 -07:00
David Aguilar b2ec47f2f0 views.dag: Read history in a background thread
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-05-04 00:18:58 -07:00
David Aguilar 90271f4af7 views.dag: Tighten padding around the scene rect
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-05-04 00:16:24 -07:00
David Aguilar 16676b6f1e views.dag: Increase mousewheel velocity
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-05-04 00:15:42 -07:00
David Aguilar feb81bf0f4 commit: Add a history traversal iterator
RepoReader streams git rev-list for reading history fom git.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-05-04 00:09:05 -07:00
David Aguilar e95d37b3e1 commit: Use the full SHA-1 internally
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-05-04 00:05:55 -07:00
David Aguilar 4460ab0c55 commit: Make str() return the SHA-1
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-05-04 00:04:20 -07:00
David Aguilar 1591217955 doc: Document models.commit
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-05-04 00:03:39 -07:00
David Aguilar 168c7aa7e9 views.main: Remove tooltip from the status view too
That widget is also informational so we don't want to block it with
the tooltip message.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-05-04 00:03:07 -07:00
David Aguilar 752367ee92 views.dag: Allow running views.dag from the source tree
% python cola/views/dag.py

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-05-04 00:00:24 -07:00
David Aguilar 876b5d2694 views.dag: Remove unused import
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-05-04 00:00:24 -07:00
Uri Okrent c91b6ae972 compat: Make a fake hashlib for use on python < 2.5
Signed-off-by: Uri Okrent <uokrent@gmail.com>
2010-04-28 19:06:45 -07:00
David Aguilar d889bc25e6 portability: Add support for PyQt 4.1
cola is now certified for Python 2.4 and PyQt 4.1.
Although this combination is fairly old, this allow
us to run on el5, which is still in use in many places.

Some features are disabled when using PyQt < 4.3
but most of the functionality works just fine.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-28 15:55:37 -07:00
Stefan Naewe a4bd13e365 win32: Remove /q from InnoSetup compiler to make it work
Without this patch the InnoSetup compiler errors out and doesn't
create the installer executable.

/q works fine with InnoSetup 5.1.14 but errors out starting
with 5.2.4-dev.  Modern versions (e.g. 5.3.8 and 5.3.9)
both error out without this patch.

Signed-off-by: Stefan Naewe <stefan.naewe@atlas-elektronik.com>
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-22 22:41:27 -07:00
David Aguilar d2e8706845 README: List publicate dates first, shorten 3rdparty modules section
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-21 02:06:38 -07:00
David Aguilar 2835cf60ab views.main: Limit tooltip to the status and commit widgets only
The tooltip gets in the way of the diff view so let's limit it
to just the status and commit message dock widgets.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-20 18:51:51 -07:00
David Aguilar 8bba79138d doc: Add 1.4.2 release notes
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-20 12:21:31 -07:00
David Aguilar c744c25bc6 doc: Update documentation for the DAG view and fix sphinx warnings
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-20 08:41:34 -07:00
David Aguilar 925d0ff765 doc: Use $(SED) where appropriate
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-20 01:48:23 -07:00
David Aguilar 923e431098 doc: Update copyright
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-20 01:47:53 -07:00
David Aguilar 7e47d660f6 doc: Use shell-friendly sentinel chars in conf.py.in
msysgit sed needs escaped {}s and linux sed barfs when they're escaped.
Avoid the portability issues altogether by using @@ instead.
Thanks to Stefan Naewe for pointing out the issue.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-20 01:47:43 -07:00
Stefan Naewe e73210682c doc: use \{,\} in sed expression to get version/release substitution correct
otherwise one gets e.g. "1.4.1.2 = '{version}'" in conf.py

Signed-off-by: Stefan Naewe <stefan.naewe@googlemail.com>
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-19 18:13:01 -07:00
David Aguilar 794efbc3be doc: Use 'sed' instead of 'perl' in the Makefile
There's very few places where we require perl so let's
use the more portable option whenever possible.
We were using perl to do string replacements when
generating conf.py.  We can just as easily use 'sed'
and eliminate the pipe while we're at it.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-17 14:31:32 -07:00
David Aguilar 818edc00d7 doc: Update the contributors list to include Stefan Naewe
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-17 14:31:32 -07:00
Stefan Naewe 6e5c7d65fa doc: don't let perl do inplace modifications of conf.py
Perl always gives the error "Can't do inplace edit on conf.py: Permission denied."
when trying to build any documentation.
This replaces inplace modifications by command piping.

Signed-off-by: Stefan Naewe <stefan.naewe@googlemail.com>
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-17 14:17:31 -07:00
David Aguilar bd86c6f1e2 gitcmds: Fix merge_message() when FETCH_HEAD doesn't exist
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-16 02:04:23 -07:00
David Aguilar b7525b6072 doc: Fold AUTHORS file into the intro page for git-cola
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-14 00:21:06 -07:00
Uri Okrent f0f8ff7e20 cola.views.remote: qt 4.5, QLineEdit.setFocus takes no arguments
Signed-off-by: Uri Okrent <uokrent@gmail.com.com>
2010-04-13 13:27:22 -07:00
Uri Okrent 76e47a0d2f difftool: running on modified+staged path shouldn't show staged changes
Running difftool on a path that is both modified and staged can be done
from two spots in the repo status tool, the staged version of the path
and the modified version of that path.  The difftool run on the staged
version was correctly only showing the staged changes, but run on the
modified section was showing both the staged and modified changes, when
it should really leave out the staged changes.

Signed-off-by: Uri Okrent <uokrent@gmail.com>
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-13 13:22:02 -07:00
David Aguilar dd17e89996 views.actions: Add a command wrapper for run_command and use it
This removes the GUI dependency from the RunConfigAction command object
introduced in a09a151147.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-13 01:16:53 -07:00
David Aguilar fd8706cd24 commands: Use utils.run_command() instead of subprocess.call()
It's best to avoid using call() when we can use subprocess with shell=True.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-13 01:16:53 -07:00
David Aguilar eaef138da2 commands: Expand title again after setting environment variables
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-13 01:16:53 -07:00
David Aguilar 014e0e7b62 views.command: Remove reference to non-existent self.start variable
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-13 01:16:53 -07:00
David Aguilar 1f08e64fc4 views.actions: Fix action dialog when revprompt is true and argprompt is false
You can't hide a layout; you have to hide its members.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-13 01:16:53 -07:00
David Aguilar 88ae28e22f views.command: Support Unicode strings in the command console
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-13 01:16:53 -07:00
David Aguilar 7b8d379d45 commands, views.actions: Fix typos and import QtCore.SIGNAL()
The mistyped signal was preventing stderr from being reported.
Also, use SIGNAL() to shorten long lines.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-13 01:16:53 -07:00
David Aguilar 16c7f267d8 utils: Add run_command() from git.git
We should probably move all of the infrastructure over to using
run_command().  It's probably better to just use communicate().

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-13 01:16:53 -07:00
David Aguilar 52b3a0c6e0 Add Michael Geddes to the authors file
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-13 01:16:53 -07:00
Michael Geddes a09a151147 Implement command console for configured actions
This adds the command output console as described in git-config(1).

Signed-off-by: Michael Geddes <michaelg@rbionline.com>
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-13 00:59:12 -07:00
David Aguilar e3d9abaded cola.main: Update the "Here be dragons" trace message
It's probably about time we start adding some easter eggs.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-08 01:46:54 -07:00
David Aguilar 49107e6dfd controllers.remote: DTRT when doing fetch, push, pull
This sets appropriate defaults for the remote dialogs so that
we can simply click 'fetch', 'push', or 'pull' and it'll do the
normal expected thing, such as:

	git fetch origin
	git push origin master
	git pull origin master

Closes #43

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-08 01:24:31 -07:00
David Aguilar 8c0df5e3b8 controllers.remote: Filter remote branches when selecting remotes
This uses a fnmatch pattern to find remote names matching
remote/*.  This is a common enough convention that we should
do it by default.  If nothing matches we fallback to showing
everything.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-08 01:24:31 -07:00
David Aguilar 8234444bd7 views.remote: Give focus to the topmost widget
This adds some action-dependent behavior for giving the appropriate
widget focus.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-08 01:24:31 -07:00
David Aguilar 29ab67ee1b views.remote: Refactor layout to make sense for fetch
Widget layout is neatly tucked away in a separate functions.
This let us easily special-case fetch to move the remote to
the top of the GUI.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-08 01:24:31 -07:00
David Aguilar 8b0dfd84c4 controllers.remote: Observe a clone of the model
This is a transient dialog and shouldn't affect the app model's
state so we should hande it a clone.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-08 01:24:31 -07:00
David Aguilar 58dfe801d3 models.main: Add a serialilzer to run generate_remote_helpers()
We can't just clone() because we lazily generate the remote
helper functions (fetch, push, pull).  The serializer class
handles calling the helper after cloning.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-08 01:24:31 -07:00
David Aguilar ab164ab3c3 commands: Support guitool.*.{arg,rev}prompt booleans
Check for the value True so that we don't try to set
text fields using a boolean.

git-config(1) says that the *prompt variables can either
be a boolean or a string to display.  This teaches the
action dialog to correctly handle booleans.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-07 03:31:43 -07:00
David Aguilar d08f9d07de views.revselect: Don't provide a default revision
It's better to make users give us an explicit expression
instead of assuming that 'HEAD' is always a good default.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-07 03:29:10 -07:00
David Aguilar 31266c7ee2 commands: Use prompt_user() instead of signalling information
This reads closer to what is going on -- we're prompting the
user.  Other signals don't have an interactive component
and thus use the regular notification->command map.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-07 03:27:18 -07:00
David Aguilar 1fefb8ac54 gitcfg: Support 'yes' and 'no' as valid booleans
Careful reading of git-config(1) revealed a bug in our
implementation.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-07 03:25:22 -07:00
David Aguilar e9eb3aafa8 commands: Add a RunConfigAction command
This implements the git guitool.*.cmd interface as documented
in git-config(1).

Closes #44

Suggested-by: frogonwheels via github
References: http://www.kernel.org/pub/software/scm/git/docs/git-config.html
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-06 21:30:41 -07:00
David Aguilar dfe9ddc264 commands: Add a todo note to use mimetypes 2010-04-06 21:19:37 -07:00
David Aguilar dea0d057cd views: Add a command wrapper for the action runner dialog
This will allow us to run the dialog from the command object
through dependency injection.  GUIs register themselves with
the factory through which the commands ask for user input.
This keeps things decoupled yet flexible.
The following commits will integrate this into the main GUI.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-06 21:18:54 -07:00
David Aguilar 5d08751089 guicmds: Add a command wrapper for question() and information()
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-06 21:13:46 -07:00
David Aguilar 71da727f0f guicmds: Fix docstring :param: syntax.
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-06 21:13:31 -07:00
David Aguilar 4fd4187ede doc/views: Remove unused misc.rst
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-06 21:12:38 -07:00
David Aguilar 24f74bf25c doc/views: Document dag and add dag + revselect to the index
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-06 21:11:10 -07:00
David Aguilar 719d6dbb59 doc: Document the cola.qt module
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-06 21:09:26 -07:00
David Aguilar ce671cc42d doc: Remove controllers.util
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-06 21:08:01 -07:00
David Aguilar 0ce2adafb0 views: Add revselect for selecting revisions
This dialog is actually a generic "radiobutton +
listwidget driven textbox" but we use it for selecting git refs.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-06 21:07:07 -07:00
David Aguilar 90e2540f50 views: Add revselect for selecting revisions
This dialog is actually a generic "radiobutton +
listwidget driven textbox" but we use it for selecting git refs.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-06 21:06:55 -07:00
David Aguilar 63f20c460d fixup "classic: Reuse code during startup" 2010-04-06 21:04:58 -07:00
David Aguilar 98225274d2 gitcfg: Add helpers for guitool queries
These make it possible to ask for all options for a particular
guitool.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-06 21:02:53 -07:00
David Aguilar 7a644902fd gitcfg: Implement all() and find() for config queries
find() lets us search for config names with fnmatch globs.
all() makes it all possible by providing a flattened config
namespace.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-06 20:58:59 -07:00
David Aguilar 1a989e31b8 classic: Reuse code during startup
This simplifies main.py and that's good.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-06 20:56:40 -07:00
David Aguilar 594c4838c6 views.repo: Allow selecting directories
We just flag 'em as 'staged' when setting the selection.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-06 20:53:10 -07:00
David Aguilar 25044f6138 views.main: Unparent the DAG and classic apps
This lets us put these in the background, etc.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-06 20:52:03 -07:00
David Aguilar 562e3b06a8 signals: Add 'question'
We can do this now so let's start to use the ui factory
when writing commands.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-06 20:50:26 -07:00
David Aguilar 41e2f581e6 dag: Use center_on_screen() when standalone
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-06 20:47:24 -07:00
David Aguilar d4408343aa gitcmds: Fix docstring syntax for :rtype:
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-06 20:46:55 -07:00
David Aguilar c3db49ca10 qtutils: Add center_on_screen()
Helper function to place widgets in a simple location.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-06 20:45:03 -07:00
David Aguilar 90596ebd62 gitcmd: Update docstring to reflect usage
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-06 20:37:38 -07:00
David Aguilar 60cf6d0c4b views.standard: Raise windows before showing them
Prevent dialogs from popping by ensuring they're in the foreground
before showing them.  Ya, we probably shouldn't hack show()
to mean show() + raise_(), but it's worked so far...

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-06 20:34:37 -07:00
David Aguilar 6ef1c4d4d7 models.selection: Add 'filename' for returning a single path
This returns the first item that it finds in its various
staged, unstaged, etc. lists.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-06 20:12:25 -07:00
David Aguilar a98a95542e cmdfactory: Add 'prompt_user' for delegating user interaction
The command factory now allows us to prompt for user inputs.
This is done through a "Command Wrapper" class that is installed
for each parameter that needs user interaction from a command.

This allows the commands to be decoupled from their GUI
components.  The dependencies are injected at runtime by
the GUI.  Once the wrappers are in place then commands
can prompt for input by going through the command factory.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-06 20:11:34 -07:00
David Aguilar 8803186970 notification: Rename 'listen' to 'connect' for consistency
This makes the notification module more closely match the Qt
signals/slots API.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-06 15:45:23 -07:00
David Aguilar 84e74ae26f views.main: Factor out callbacks into guicmds
This removes another few hundreds lines of callback code
from the main GUI.  We also load less at startup.
controllers/util.py was folded into guicmds as well.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-04 01:01:24 -07:00
David Aguilar 83d8a45bee git: Log commands when GIT_COLA_TRACE is defined
Emit logging messages when the GIT_COLA_TRACE environment
variable is defined.  This lets users see the various git
commands run by cola during its execution.

	$ env GIT_COLA_TRACE=1 git cola

Closes #39

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-03 23:28:00 -07:00
David Aguilar e26ceb332a views.dag: Draw decorations with a yellow background
Branches and tags are all drawn inside of the same rectangle.
We may want to parse local and remote branches out separately
later so that we can draw separate items for them.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-03 22:50:31 -07:00
David Aguilar 2b03b346e3 views.dag: Draw edges and arrows using a dotted gray line
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-03 22:47:19 -07:00
David Aguilar 8418a06ff1 views.dag: Remove commented-out code
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-03 22:46:47 -07:00
David Aguilar 604eb05352 views.dag: Factor out constants for drawing edges
This is a micro-optimization and may not make much of a difference
but it doesn't hurt, either.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-03 22:46:21 -07:00
David Aguilar 0f69c32b3e views.dag: Disable moving the graph nodes
We may later enable this if needed, but for now it's
better to have a static view.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-03 22:46:21 -07:00
David Aguilar 7a72bb3377 views.dag: Pass the parent widget to the dag view
This allows us to use the StandardDialog class as the base class
for the GitDAGWidget.  A benefit is that the DAG widget is
centered relative to the git-cola window.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-03 22:46:15 -07:00
David Aguilar e61d64f258 views.dag: Use the cola.compat module for the 'set' type
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-03 22:39:08 -07:00
David Aguilar 575b268ac8 classic: Set the window title in response to model notifications
The RepoDialog observes the model so that we can detect when
we change branches, etc.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-03 22:39:08 -07:00
David Aguilar fa0c67d2f0 classic: Provide a dialog to wrap the RepoTreeView
Since we are now passing the Qt widget parent to the dialog
constructor we can do nice things like center the widget
relative to the git-cola window.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-03 22:39:08 -07:00
David Aguilar f25844ecaa models.commit: Filter out remote HEADs
Seeing origin/HEAD is probably a little confusing for new users
so filter it out of the decoration list.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-03 22:38:50 -07:00
David Aguilar a514ae4095 qtutils: Fix mistyped 'add_close_action()'
Originally mistyped as 'add_close_acction()'.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-03 22:02:33 -07:00
David Aguilar 246097d986 resources: Fallback to git-cola.txt for html_docs()
Launching 'git-cola' from the source tree won't find the html
docs by default so fallback to the asciidoc documentation.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-03 19:46:29 -07:00
David Aguilar a8fe8f498e doc: Document the --version and --git-path flags
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-03 19:46:29 -07:00
David Aguilar 8a52a8acff README: Remove group email address
Implements spam paranoia.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-03 18:50:51 -07:00
David Aguilar 1d22e4edc8 README: Shorten document and update copyright for 2010
Less is more.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-03 18:47:37 -07:00
David Aguilar 3d8c79b070 views.main: Add a tooltip to show the current directory
Show a tooltip with the current directory and branch
for the main cola interface.

Closes #45

Suggested-by: Andrew Kinney <andrew.kinney@disneyanimation.com>
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-03 17:46:52 -07:00
David Aguilar 5218878567 Merge branch 'treeview'
* treeview:
  dag: Move TODO file to the 'todo' branch
  views.dag: Show more edges
  git-dag: Show more nodes by default and limit to local branches
  views.dag: Add arrows to graph edges
  views.dag: Add GitDAGWidget
  views.dag: Add horizontal wheel panning
  views.dag: Update _zoom when scaling
  views.dag: Mouse wheel pans, add control to zoom
  views.dag: Zoom more
  Added TODO list for treeview branch
  views.main: Add 'DAG...' to the tools menu
  models.main: Factor out history methods into models.commit
  git: Remove the test for None
  Remove 'remotes/' and 'heads/' from branch names in dag view
  Add sha1 and branch/tag text to the nodes (and give them some space)
  Make room for text in the node by providing each with an internal glyph
  Set background color the simple way with the background brush
  Add additional interesting info to Commits
  treeview: add a graphical history viewer
  models: add a commits() method for traversing history
2010-04-01 16:46:55 -07:00
David Aguilar faa3eb34cf dag: Move TODO file to the 'todo' branch
The 'todo' branch has all of our meta information.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-01 16:45:55 -07:00
David Aguilar 3393af2f19 git-cola: Rework sys.path use system versions of thirdparty modules
This changes the launcher to prefer the user-installed versions
of simplejson and jsonpickle.

We want cola to work with whatever's installed on the system so
doing it this way ensures that we run against the system installed
modules whenever possible.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-04-01 16:43:26 -07:00
David Aguilar b8335b0dbb git-cola: Update copyright and license text for 2010
Remove the blurb about having received a copy of the GPL
since it is redundant.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-03-31 00:59:37 -07:00
David Aguilar b2dac151b7 resources: Document the install-docs target in html_docs()
Mention that index.html does not exist until after the install-docs
target has been run.  Update the docstrings for consistency while
we're at it.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-03-31 00:48:46 -07:00
David Aguilar 6c60f630dc i18n: Remove all references to the old qm infrastructure
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-03-31 00:44:52 -07:00
David Aguilar 0cfa2788a0 views.dag: Show more edges
We limit edges longer than 2 ** 11.  Let's try 2 ** 13 instead.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-03-29 17:25:22 -07:00
David Aguilar ea135d02b4 git-dag: Show more nodes by default and limit to local branches
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-03-29 17:25:22 -07:00
David Aguilar 31fab34793 views.dag: Add arrows to graph edges
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-03-29 17:25:22 -07:00
David Aguilar 2e4793bccb views.dag: Add GitDAGWidget
The widget supports shortcut keys such as Ctrl+w.
We might want to split up the GUI to have a details section
which can react to the selection.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-03-29 17:25:22 -07:00
David Aguilar e34b28902f views.dag: Add horizontal wheel panning
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-03-29 17:25:22 -07:00
David Aguilar 415f4c87a9 views.dag: Update _zoom when scaling
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-03-29 17:25:22 -07:00
David Aguilar 0c61bd36ec views.dag: Mouse wheel pans, add control to zoom
We should add horizontal pan too.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-03-29 17:25:22 -07:00
David Aguilar f1a8070e23 views.dag: Zoom more
1.2 is a tad too slow.  This could be a config.. or not.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-03-29 17:25:22 -07:00
Uri Okrent 9bd2cb059b Added TODO list for treeview branch 2010-03-29 17:25:22 -07:00
David Aguilar 26074b70ea views.main: Add 'DAG...' to the tools menu
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-03-29 17:25:22 -07:00
David Aguilar 863005d1ba models.main: Factor out history methods into models.commit
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-03-29 17:25:15 -07:00
David Aguilar 4085f6c9ce git: Remove the test for None
None is undefined and we're not using it.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-03-29 17:07:18 -07:00
Uri Okrent 79c5c9a32f Remove 'remotes/' and 'heads/' from branch names in dag view
Signed-off-by: Uri Okrent <uokrent@gmail.com>
2010-03-29 17:07:18 -07:00
Uri Okrent 25ae621d01 Add sha1 and branch/tag text to the nodes (and give them some space)
Signed-off-by: Uri Okrent <uokrent@gmail.com>
2010-03-29 17:07:18 -07:00
Uri Okrent 5bb03ec8ba Make room for text in the node by providing each with an internal glyph
Moved the ellipse to the side of the node (and only half the width of
the node, and provide a method of addressing the internal ellipse's
center point rather than the center point of the entire node (for use by
the edges).

Signed-off-by: Uri Okrent <uokrent@gmail.com>
2010-03-29 17:07:18 -07:00
Uri Okrent a54a407762 Set background color the simple way with the background brush
This gets rid of the artifacts we were seeing when moving around the
scene.  It isn't a fancy gradient, but oh well...

Signed-off-by: Uri Okrent <uokrent@gmail.com>
2010-03-29 17:07:18 -07:00
Uri Okrent c5b30e3593 Add additional interesting info to Commits
They now contain the short sha1, the subject, a list of parents, a list
of tags, the author's name, and the author date.

Also added a __repr__ to Commit for sanity's sake

Signed-off-by: Uri Okrent <uokrent@gmail.com>
2010-03-29 17:07:18 -07:00
David Aguilar c96e0c1e14 treeview: add a graphical history viewer
This adds the cola.views.dag history browser.
This is the beginnings of a nice QGraphicsView
based tree browser.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-03-29 17:07:18 -07:00
David Aguilar 25abaf436a models: add a commits() method for traversing history
This adds a basic commit object and commits() method to
return a commit "objects" for use in history traversal.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-03-29 17:07:18 -07:00
David Aguilar 579360d384 Mention all git-cola contributors in the AUTHORS file
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-03-29 17:06:08 -07:00
David Aguilar a3921f7c21 Use the new gettext infrastructure for i18n
This adds a cola.i18n module for doing gettext translations.
This does away with the qm-based translation system in favor
of a standard gettext setup.

We still monkey-patch QApplication.translate() with our
own custom translator.  The main difference is switching
from a Qt-specific QTranslator translation system to a
gettext-based one.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-03-29 17:06:02 -07:00
David Aguilar db70e338b8 gitcfg: Guard against moved or deleted .gitconfigs
Users can actively move or rename files so guard against
this by calling reset() whenever the cache key changes.
The original code calculated the path list once at
startup and did not ever recalculate it.  We now recalculate
the list whenever the stat info changes.

A subtle change here is that the old code would always
check $prefix/etc before checking /etc/gitconfig.
git always checks /etc/gitconfig regardless of $prefix
so we can remove that check.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-03-17 01:50:59 -07:00
David Aguilar 19db380842 gitcfg: Add a get_encoding() function
Returns gui.encoding, defaults to utf-8.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-03-17 01:38:39 -07:00
David Aguilar ff10d82de1 cola.gitcfg: Remove unused import
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-03-17 01:37:35 -07:00
David Aguilar ee2d00ffc6 Makefile: Add 'tags' to .PHONY
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-03-10 11:25:29 -08:00
David Aguilar e2526f60a4 gitcmds: Use rev-parse --symbolic-full-name
There's not much overhead in calling out to git now that
we use a stat cache on .git/HEAD.  The Python implementation
is still available as _read_git_head() and is still used
for the 'git init' case.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-03-10 11:25:29 -08:00
David Aguilar 0b2c607df2 utils: Add an is_win32() helper function
So we don't have to keep writing `if os.name in ('nt', 'dos')`.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-03-10 11:25:29 -08:00
David Aguilar ea498ede49 setup.py: Simplify by removing packages/package_data usage
Reusing the existing _app_path() is simpler.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-03-10 11:25:29 -08:00
David Aguilar 6ad4a0db46 Makefile: Add 'qm', 'pot', and 'mo' targets
The 'qm'-based i18n is currently used but we may be switching
to standard .pot and .mo files soon.  This provides the infrastructure
to get this going.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-03-10 11:25:29 -08:00
David Aguilar ab8796fc34 setup.py: Integrate build_pot, build_mo, and build_qm commands
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-03-10 11:25:29 -08:00
David Aguilar 5d113a0614 setup.py: Check Python version using platform.python_version()
This is better than stringifying sys.version_info().

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-03-10 11:25:29 -08:00
David Aguilar 15ca99f677 build: Add commands for buliding .pot, .mo, and .qm files
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-03-10 11:25:29 -08:00
David Aguilar ae1aacc692 COPYRIGHT: List individual publication dates
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-03-10 11:25:29 -08:00
David Aguilar 548f011930 Makefile: 'install' depends on 'all'
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-02-27 04:25:14 -08:00
David Aguilar 3d97704688 views.status: Improve selection handling across updates
We now manage the selection across updates much more carefully.
When items are staged they are selected and their
diff is automatically shown.  Vice-versa for unstaged.

A group of selected items retains their selection across
category moves.  If an item moves into a new category,
we scroll the viewport to the item in that category.

Priority is given to the current, partially-staged diff
when interactively staging hunks.  Otherwise, we would
be breaking context by showing the unstaged diff and then
switching to the staged fragment.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-02-27 04:24:36 -08:00
David Aguilar cf5fdeb998 views.status: Use QTreeWidget.indentation() instead of magic numbers
Qt knows the size of the tree widget's indentation so use a derived
value instead of hard-coding the x-offset to the icon.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-02-27 04:17:55 -08:00
David Aguilar 5eed0c51cd views.status: Process click-to-un/stage on mouse release
Doing this on mouse press was causing an odd interaction with
the selection.  Defer until mouse release so that the selection
is applied before this event occurs.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-02-27 04:14:31 -08:00
David Aguilar 226a5ab1c2 views.status: Remember scrollbar positions across updates
Restore the vertical scrollbar's position when model updates occur.
This keeps the viewport from moving around too much.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-02-27 03:59:24 -08:00
David Aguilar c7799976e1 Use 'gvim' when the editor is 'vim'
Some users might have $EDITOR configured to 'vim'.
We should redirect these calls to 'gvim' to avoid
creating a zombie editor.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-02-23 17:04:48 -08:00
David Aguilar 9e06f3ec05 Honor the $VISUAL environment variable
$VISUAL defines the name of a visual editor and should be
used in favor of $EDITOR.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-02-23 16:43:39 -08:00
David Aguilar 607ea6ddc8 Makefile: Defer calculating $(COLA_VERSION)
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-02-19 16:38:01 -08:00
David Aguilar 607a4f4d96 Makefile: Add tests to 'tags' target
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-02-19 16:37:26 -08:00
David Aguilar 0816b99cfb darwin/name-tarball.py: Add support for snow leopard
Recognize '10.6' as snow_leopard.
Additionally, not knowing the mac platform shouldn't
prevent us from being able to name the tarball.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-02-12 23:51:32 -08:00
David Aguilar 08dea92585 darwin: Update README to mention .app workarounds
Testing .app bundles is non-trivial on PPC Tiger machines
so mention the testing workaround mentioned on the git-cola
mailing list.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-02-11 01:43:28 -08:00
David Aguilar 8fbd9e31f5 git: Pass --no-color when calling 'git log'
Users can specify 'git config color.ui always' in  git config
so pass --no-color to override the configuration.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-02-08 11:28:01 -08:00
David Aguilar 53e98570c4 controllers.selectcommits: Do not pass string to QObject.connect()
QObserver was recently reworked to not override QObject.connect().
When this happened it stopped automatically wrapping signal names
with SIGNAL().

Do the mainstream Qt thing and wrap the signal name with SIGNAL().

Fixes #42

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-02-02 22:05:12 -08:00
David Aguilar 69c900775b win32: Add system-standard paths to create-installer.sh
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-02-02 17:05:49 -08:00
David Aguilar 933d2f8a0a win32: Use a relative directory when building installer
Otherwise setup.py writes files to the wrong directory.

Fixes #40

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-02-02 16:48:20 -08:00
David Aguilar 71d59c23dc win32: Remove colon from application name
The Windows installer suggested
"git-cola : a highly caffeinated git GUI" as the start-menu entry.
This is an invalid folder-name, and the installer subsequently
refuses to use it unless changed.

Fixes #41

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-02-02 16:15:01 -08:00
David Aguilar 675674cc67 review mode: Allow reviewing the current branch
Review mode used to always find the merge-base with HEAD.
We now search for a tracked branch and then fallback to
'master' if no tracked branch exists.

This is consistent with the common topic-branch workflow.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-02-01 15:39:36 -08:00
David Aguilar 80205ea16f gitcmds: Fix uninitialized variable error
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-02-01 15:35:23 -08:00
David Aguilar d129d25466 views.status: Add submodule menu actions
This adds menu entries for staging submodule changes and
launching git-cola inside the submodule.

Closes #17

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-01-19 00:17:40 -08:00
David Aguilar 7c693f91d3 views.main: Use different context menus for submodules
We can now stage submodules and launch git-cola on a submodule
from within the submodule diff text context menu.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-01-18 23:32:47 -08:00
David Aguilar e268f4e926 models.main: Keep track of submodule paths
We now keep track of submodules as reported by diff-index.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-01-18 23:32:16 -08:00
David Aguilar 8dc7188d47 views.mainwindow: Use the 'Qt' module directly
We already imported 'Qt' from 'QtCore' so there's no
need to reference it via 'QtCore.Qt' when we can simply
say 'Qt' instead.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-01-18 23:31:24 -08:00
David Aguilar 1182bb8217 qobserver, views.main: Always use setPlainText()
setText() guesses the type based on content.
We *always* want plain text, so use setPlainText() instead.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-01-18 23:29:58 -08:00
David Aguilar 4d352f58b4 Use 'git diff --submodule' when available
The first steps towards better submodule support.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-01-18 23:12:16 -08:00
David Aguilar cf0f4be5d1 qt: Make it easier to resize QFlowLayoutWidgets
This adds a size policy so that QFlowLayoutWidgets are easier
to resize.  They are no longer constrained to the size of their
child widgets.

As a consequence, the "Actions" dock can now be resized to be
in its vertical orientation even when space is limited.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-01-17 13:07:00 -08:00
David Aguilar 85307ee9a1 mainwindow: Make the margin around the commit buttons larger
This adds some spacing above the Sign Off/Commit buttons.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-01-17 13:03:18 -08:00
David Aguilar d42b9dcafc views.mainwindow: Use a QFlowLayoutWidget for the actions dock
This adds a new box layout widget that changes its orientation when it
is resized.  This allows for a more flexible arrangement of the GUI.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-01-16 16:09:07 -08:00
David Aguilar aa29b28853 doc: Add Boris to the contributors list
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-01-15 23:06:22 -08:00
Boris W e4b8dde05b mainwindow: Add alt-key shortcuts
This incorporates alt-key shortcuts into the Qt menu.

Signed-off-by: Boris W <boris@tetago.com>
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-01-13 00:20:20 -08:00
David Aguilar ba564772c6 cola.git{cfg,cmds}: Use an os.stat() cache
We now use stat.st_mtime to tell us when the current branch or
git config data has changed.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-01-11 10:23:28 -08:00
David Aguilar 9b156099c6 doc: Do not mention GIT_DIFF_TOOL
The fact that difftool honors GIT_DIFF_TOOL is an implementation
detail and should never have been mentioned in the documentation.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-01-09 18:27:57 -08:00
David Aguilar 0668b4a380 difftool: Do not consider GIT_MERGE_TOOL when setting the difftool
We should have never supported both GIT_DIFF_TOOL and GIT_MERGE_TOOL.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-01-09 18:27:12 -08:00
Iulian Udrea 7458da3455 setup.py: Fix 'shutil' usage
Import the missing 'shutil' module and fix its misspelling.

Signed-off-by: Iulian Udrea <iulian@ubuntu.com>
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-01-05 13:25:47 -08:00
David Aguilar 208960acc7 qobserver: Do not issue notifications for textfields in SLOT()
This simplifies the handling for text fields by removing the
_in_textfield and _in_callback state variables.

In order to avoid loopbacks we simply pass notify=False when
setting the model parameters for text fields in SLOT().

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-01-04 17:38:23 -08:00
David Aguilar accff05a48 qobserver: Do not override QObject.connect()
This removes the hacky "always wrap connect the signal string in SIGNAL()"
connect() method implemented in QObserver.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-01-04 17:26:57 -08:00
David Aguilar a11af49e60 models.compare: Add missing 'describe()' method
This method was dropped when the compare data was factored
out of the main model.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-01-04 14:36:15 -08:00
David Aguilar 75185df70f models.base: Remove serialization methods
All serialization logic lives in cola.serializer so it can now
be removed from the model.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-01-04 11:49:55 -08:00
David Aguilar 6b5ce5f4bc controllers.bookmark: Use cola.serializer when saving bookmarks
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-01-04 11:48:53 -08:00
David Aguilar 87c984e371 controllers.repobrowser: Use a BrowserModel when selecting files
We now use a consistent model when dealing with the repobrowser.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-01-04 11:48:27 -08:00
David Aguilar 548198abc6 models.observable: Add a serialization handler for ObservableModel
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-01-04 11:46:09 -08:00
David Aguilar 464d651068 controllers: Use cola.serializer's clone() function
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-01-04 11:45:36 -08:00
David Aguilar dd945441ed settings: Use cola.serializer for saving the SettingsModel
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-01-04 10:57:20 -08:00
David Aguilar 7d3d60322b cola: Add cola.serializer for handling object serialization
This will contain the serialization functionality currently
coupled to the Model class.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-01-04 10:55:12 -08:00
David Aguilar 8388f7e4c6 cola: Add a cola.compat module for python2.4 compatibility
cola.compat provides the 'set' class for python2.4.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-01-04 10:25:22 -08:00
David Aguilar d50ea82596 COPYRIGHT: 2010 update
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-01-04 10:22:40 -08:00
David Aguilar bab6aa62bf .gitignore: Consolidate subdirectory entries
A single file is easier to maintain.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-01-04 10:22:23 -08:00
David Aguilar 3f0e8bbdc9 cola: Move simplejson and jsonpickle modules to thirdparty/
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-01-04 10:21:11 -08:00
David Aguilar 177eed86f3 gitcmds: Use plumbing to implement unstage_paths()
Fallback to git-update-index instead of git-add when git-reset
exits because we are on a branch that is yet to be born.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-01-02 12:14:04 -08:00
David Aguilar 3beabc69da doc: Use the phrase 'git GUI' when describing git-cola
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-01-02 12:14:04 -08:00
David Aguilar 325857d317 doc: Remove '*' pattern from _templates/.gitignore
That's the wrong pattern to use because it prevents us from
using templates in the future.  If we start using our own Sphinx
templates later then we can remove .gitignore altogether.

Signed-off-by: David Aguilar <davvid@gmail.com>
2010-01-02 12:14:04 -08:00
David Aguilar 8525f78db2 doc: Remove unnecessary .gitignore in the _static/ directory
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-01-02 12:14:03 -08:00
David Aguilar 1034abdfeb doc: Remove redundant bullet item from release notes
Signed-off-by: David Aguilar <davvid@gmail.com>
2010-01-02 12:14:03 -08:00
David Aguilar 042dee98d4 doc: Add 'core git hackers' to the 'Thanks to' section
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-31 18:01:24 -08:00
David Aguilar fb3bd7ea6b doc: Add missing screenshots
Darn you .gitignore!

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-31 17:58:41 -08:00
David Aguilar 4a926060c5 resources: Link to the Sphinx HTML documentation
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-31 17:13:37 -08:00
David Aguilar 6ba1873a29 doc: Add Iulian Udrea to the contributors list
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-24 16:57:48 -08:00
David Aguilar c67f76f202 doc: Update 1.4.1.2 release notes to mention bookmarks-at-startup
We now present a list of bookmarks when run outside of a git
repository so mention it in the release notes.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-24 16:52:11 -08:00
David Aguilar 0d82f79d86 doc: Add a page for the 'Actions' tool
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-24 16:51:07 -08:00
David Aguilar 3a7f61bfc3 tools: Add a page for the 'Commit Message Editor' tool
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-24 16:50:50 -08:00
David Aguilar ec45080d36 doc: Add a page for the 'Diff Viewer' tool
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-24 16:50:22 -08:00
David Aguilar 038cc52d14 doc: Add an 'Actions' section to the status tool page
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-24 16:49:49 -08:00
David Aguilar 89b04a638a doc: Refer to 'Diff Viewer' in the mainwindow page
The new interface says 'Diff Viewer' so we should use that.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-24 16:49:21 -08:00
David Aguilar 31d1ce81fd doc: Add a list of contributors to the git-cola intro page
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-24 16:48:32 -08:00
David Aguilar f859aba466 doc: Update intro headings to say 'Contribute' and 'Git Cola?'
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-24 16:47:48 -08:00
David Aguilar 7fe339e954 views.main: Change 'Undo Selection' to 'Undo Selected Lines'
This makes the diff viewer context menu entries more consistent.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-24 15:58:00 -08:00
David Aguilar 964d6eebc4 INSTALL: Mention the 'standalone' make variable
This adds an example of the 'standalone' make variable for use
by packagers.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-24 15:57:25 -08:00
David Aguilar d0f1ab5fe5 Merge branch 'show-bookmarks-at-startup'
* show-bookmarks-at-startup:
  views.startup: Always create bookmark widgets
  Add bookmark list to startup window.
2009-12-24 02:20:27 -08:00
David Aguilar 7e5d58893c doc: Update 1.4.1.2 release notes
Mention the inotify non-existent directories fix.
Mention the 'make standalone=true' variable.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-24 01:55:14 -08:00
David Aguilar d34ae77e80 doc: Fix formatting for the 'cola.showoutput' release note
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-24 01:53:58 -08:00
David Aguilar 9bdc952237 views.startup: Always create bookmark widgets
We now unconditionally create bookmark widgets and
hide them when no bookmarks exist.

This also modifies the code to use spaces instead of tabs.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-24 01:37:28 -08:00
David Aguilar 94ce5dd92c Update to the latest jsonpickle
I'd rather not keep a copy of this here but py2app and the
win32 installer both need them.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-24 00:57:02 -08:00
David Aguilar d42fd033ef Update to the latest simplejson
I'd rather not keep a copy of this here but py2app and the
win32 installer both need them.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-24 00:57:02 -08:00
David Aguilar d4d68604f5 Makefile: Exclude jsonpickle and simplejson from nosetests
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-24 00:54:50 -08:00
David Aguilar 6466941893 inotify: Don't attempt to watch non-existant directories
This handles the case where directories are deleted
but not yet committed.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-24 00:14:46 -08:00
David Aguilar 3b562335ae Makefile: Support setup.py's --standalone option
This allows the Makefile to pass --standalone to setup.py
when the 'standalone' make variable is supplied.

    make standalone=1 install

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-23 23:43:15 -08:00
David Aguilar fb3445a09f setup.py: Add --standalone to avoid installing 3rdparty libs
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-23 23:40:16 -08:00
David Aguilar fc1bdccb9f win32/create-installer.sh: Simplify installation
There's no need to check for meta/ReleaseNotes.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-23 23:37:46 -08:00
Daniel King 7cdcc37bd4 Add bookmark list to startup window. 2009-12-24 15:37:12 +08:00
David Aguilar 9139be9be9 win32/create-installer.sh: Simplify setup.py usage
There's no need to pass --root to setup.py.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-23 23:36:44 -08:00
David Aguilar 9552c0f853 setup.py: Remove unused _run_cmd() function
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-23 23:33:35 -08:00
David Aguilar d2e0a7e499 MANIFEST.in: Don't include forked jsonpickle
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-23 23:33:01 -08:00
David Aguilar 7b9adcee4b .gitignore: Add debian/ entries
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-22 11:40:31 -08:00
David Aguilar d63a636d79 doc/Makefile: Remove all of _build/ when cleaning
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-22 01:24:38 -08:00
David Aguilar a583324ff1 doc: Remove intermediate 'release notes' page
We don't need no stinkin subdir.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-22 01:24:09 -08:00
David Aguilar c611996663 doc/.gitignore: Move _build exclude to top-level doc directory
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-22 00:17:42 -08:00
David Aguilar b82ca69d69 Makefile: Don't unnecessarily set DESTDIR
Otherwise we break naive config.mak with prefix=~/subdir.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-22 00:07:41 -08:00
David Aguilar 81d40cbdac doc: Update 1.4.1.2 release notes
We now mention the resized remote dialogs and the
the new 'make dist' target.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-21 22:46:20 -08:00
David Aguilar a3d0080bb3 doc: Add git-cola-1.4.1.2 release notes
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-21 22:42:10 -08:00
David Aguilar d803b497cc doc: Add a "page" header to relnotes/index
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-21 22:40:54 -08:00
David Aguilar ffaf968bf2 views.remote: Make the 'remote' dialog larger by default
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-21 22:28:51 -08:00
David Aguilar 7a23888cb6 doc: Mention drag&drop and GUI layouts in 'mainwindow'
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-21 22:24:45 -08:00
David Aguilar 3439b0e6ce doc: Add a 'tools' page and link 'status' beneath it
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-21 22:24:02 -08:00
David Aguilar 828c715d36 doc: Remove unneeded sourcecode section
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-21 22:23:10 -08:00
David Aguilar e9463b2307 views.status: Add 'Remove Unstaged Edits' for staged items
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-19 16:30:49 -08:00
David Aguilar 618cf878a4 views.status: Add 'Remove Unstaged Edits'
This makes adds a new 'remove unstaged edits'
action and makes the language consistent in the
existing uncommited edits action.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-19 16:16:15 -08:00
David Aguilar 3a9f4b92f9 doc/intro: Add a 'Running git-cola' section
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-18 13:17:23 -08:00
David Aguilar 2f15ffa27b Makefile: Add 'dist' target
The 'dist' target generates a tarball.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-18 12:57:45 -08:00
David Aguilar b69bdc4868 setup: Support a 'version' stamp file in dist tarballs
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-18 12:57:10 -08:00
David Aguilar 0567515572 main: Don't run git update-index on git cola version
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-18 12:55:39 -08:00
David Aguilar 8c6478b5f3 version: Trivial style cleanup
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-18 12:47:50 -08:00
David Aguilar 8bea3ce158 doc: Break out userguide into intro, mainwindow, shortcuts
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-18 15:53:24 -08:00
David Aguilar a48717e129 Makefile: Allow local customizations via config.mak
We can now keep around custom settings for Makefile
variables by using a 'config.mak' file.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-18 15:33:07 -08:00
David Aguilar 5eae81ce4d Makefile: Pass $(prefix) to the doc targets
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-18 15:31:48 -08:00
David Aguilar a902886dfe .gitignore: Remove old/obsolete entries
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-18 15:31:20 -08:00
David Aguilar e62d16db14 views.status: Add missing 'utils' import
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-14 15:20:16 -08:00
David Aguilar 5d7cb0fa39 releases: Mention the desktop entry name change
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-11 21:49:30 -08:00
David Aguilar 6af5d67683 doc: Rearrange section titles to improve the table of contents
This rearranges the table of contents so that things are easier
to find.  Notably, the release notes section now has a maxdepth
setting to prevent showing details about each release
on the index page.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-11 21:36:51 -08:00
David Aguilar dbfa6ac0c2 doc: Fix section titles to use correct underline
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-11 21:34:43 -08:00
David Aguilar f1a7d53f78 doc: Add git-cola-1.4.1.1 release notes
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-11 11:58:09 -08:00
David Aguilar 0592f94328 controllers.remote: Fix indentation error when pushing
The code to warn users about pushing new branches was
accidentally outdented.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-11 10:55:16 -08:00
David Aguilar 91196651d2 Merge commit 'mathstuf/master'
* commit 'mathstuf/master': (21 commits)
  More descriptive name for the menu entry
  models.gitrepo: Use NULL to delimit fields instead of slash
  views.repo: Add a shortcut for closing the classic widget
  qtutils: Add add_close_action()
  gitcmds: Remove the _use_patience variable
  version: Cache the output of 'git version'
  gitcmds: Use cola.version for patience
  version: Speed up version checks by using a cache
  Make our diffs more patient -- patience is a virtue
  qtutils: Do not re-translate strings
  cola.app: Guard against QString for PyQt4.3 compatibility
  doc: Mention push behavior in the release notes
  controllers.remote: Warn when creating a new branch
  INSTALL: Update download urls
  gitcmds: Fix invalid signature for merge_message()
  models.main: Save window settings by default
  views.mainwindow: Use a nicer default dock arrangement
  views.mainwindow: Use a larger default window size
  doc: Do not mention unused cola.geometry variable
  doc: Document the 'git cola --classic' option
  ...
2009-12-09 23:34:44 -08:00
Ben Boeckel 5455f1e682 More descriptive name for the menu entry 2009-12-10 01:06:33 -05:00
David Aguilar 8178833b4c models.gitrepo: Use NULL to delimit fields instead of slash
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-10 01:06:33 -05:00
David Aguilar 096cd5c8be views.repo: Add a shortcut for closing the classic widget
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-10 01:06:33 -05:00
David Aguilar c3995e7399 qtutils: Add add_close_action()
add_close_action() adds a Ctrl+w shortcut for closing a widget.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-10 01:06:33 -05:00
David Aguilar 1b7198c8ce gitcmds: Remove the _use_patience variable
We cache the output of 'git version' now so there's no need to
store the _use_patience calculation.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-10 01:06:33 -05:00
David Aguilar 0162327e3f version: Cache the output of 'git version'
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-10 01:06:33 -05:00
David Aguilar 9b332ca960 gitcmds: Use cola.version for patience
This prevents running 'git version' everytime we run 'git diff'.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-10 01:06:33 -05:00
David Aguilar 9e2e175e94 version: Speed up version checks by using a cache
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-10 01:06:32 -05:00
Uri Okrent 31980b4ab4 Make our diffs more patient -- patience is a virtue
Make --patience the default for diffs produced by cola, if the git
version supports it (that is the user's git client is at least 1.6.2).

Maybe this should be controlled by a configuration somewhere, but I'm
too lazy to make that work now. Also, git config doesn't seem to mention
any existing config for controlling whether or not diff is patience by
default so it doesn't seem like cola should be the thing deciding that.
Is there any downside to always using patience diff?

Signed-off-by: Uri Okrent <uokrent@gmail.com>
2009-12-10 01:06:32 -05:00
David Aguilar 40d52deba1 qtutils: Do not re-translate strings
This avoids calling QApplication.translate()
on a string that has already been translated.

Closes #34

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-10 01:06:32 -05:00
David Aguilar 8e0ff16340 cola.app: Guard against QString for PyQt4.3 compatibility
Older versions of PyQt4 do not accept QStrings in calls
to QApplication.translate().  Guard against this situation
by wrapping text to translate in str().

Closes #34

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-10 01:06:32 -05:00
David Aguilar 20b7cf4302 doc: Mention push behavior in the release notes
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-10 01:06:32 -05:00
David Aguilar 86e8abbba8 controllers.remote: Warn when creating a new branch
Closes #35

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-10 01:06:32 -05:00
David Aguilar c79c25d960 INSTALL: Update download urls
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-10 01:06:32 -05:00
David Aguilar 7007ce8126 gitcmds: Fix invalid signature for merge_message()
We forgot to remove the 'self' argument when this got
factored out into the gitcmds module.

Closes #36

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-10 01:06:32 -05:00
David Aguilar 712c5066f3 models.main: Save window settings by default
It's the user-friendly thing to do.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-10 01:06:32 -05:00
David Aguilar f323a00905 views.mainwindow: Use a nicer default dock arrangement
This adds the missing statusdockwidget and moves things
around so that the diff is on the bottom and the top
has an arrangement of [commit, status, actions].

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-10 01:06:32 -05:00
David Aguilar 63a9ab32e2 views.mainwindow: Use a larger default window size
The Fibonacci ratio is quite nice.
2009-12-10 01:06:32 -05:00
David Aguilar 08dbc128ce doc: Do not mention unused cola.geometry variable
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-10 01:06:32 -05:00
David Aguilar a20c7f5083 doc: Document the 'git cola --classic' option
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-10 01:06:32 -05:00
David Aguilar 5cefaa08f0 models.gitrepo: Use NULL to delimit fields instead of slash
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-08 00:07:53 -08:00
David Aguilar 31a79a9840 views.repo: Add a shortcut for closing the classic widget
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-08 00:03:32 -08:00
David Aguilar 60b29e6d68 qtutils: Add add_close_action()
add_close_action() adds a Ctrl+w shortcut for closing a widget.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-08 00:03:20 -08:00
David Aguilar 42c2db8d17 gitcmds: Remove the _use_patience variable
We cache the output of 'git version' now so there's no need to
store the _use_patience calculation.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-07 23:55:30 -08:00
David Aguilar d6a84ac8bd version: Cache the output of 'git version'
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-07 23:54:23 -08:00
David Aguilar d2d70ff708 gitcmds: Use cola.version for patience
This prevents running 'git version' everytime we run 'git diff'.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-07 00:24:28 -08:00
David Aguilar 7a1760e0f8 version: Speed up version checks by using a cache
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-07 00:16:15 -08:00
Uri Okrent cb070daf2c Make our diffs more patient -- patience is a virtue
Make --patience the default for diffs produced by cola, if the git
version supports it (that is the user's git client is at least 1.6.2).

Maybe this should be controlled by a configuration somewhere, but I'm
too lazy to make that work now. Also, git config doesn't seem to mention
any existing config for controlling whether or not diff is patience by
default so it doesn't seem like cola should be the thing deciding that.
Is there any downside to always using patience diff?

Signed-off-by: Uri Okrent <uokrent@gmail.com>
2009-12-06 23:57:44 -08:00
David Aguilar 8b08a4dea6 qtutils: Do not re-translate strings
This avoids calling QApplication.translate()
on a string that has already been translated.

Closes #34

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-04 16:03:08 -08:00
David Aguilar 320df6bd0a cola.app: Guard against QString for PyQt4.3 compatibility
Older versions of PyQt4 do not accept QStrings in calls
to QApplication.translate().  Guard against this situation
by wrapping text to translate in str().

Closes #34

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-04 03:31:46 -08:00
David Aguilar 99d0bb064e doc: Mention push behavior in the release notes
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-03 22:19:02 -08:00
David Aguilar 74c2ee9738 controllers.remote: Warn when creating a new branch
Closes #35

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-03 22:16:36 -08:00
David Aguilar 4dfbf66e83 INSTALL: Update download urls
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-03 21:27:40 -08:00
David Aguilar 57850301f9 gitcmds: Fix invalid signature for merge_message()
We forgot to remove the 'self' argument when this got
factored out into the gitcmds module.

Closes #36

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-03 19:53:16 -08:00
David Aguilar de32d16dc7 models.main: Save window settings by default
It's the user-friendly thing to do.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-02 23:29:22 -08:00
David Aguilar e2cf5f0a53 views.mainwindow: Use a nicer default dock arrangement
This adds the missing statusdockwidget and moves things
around so that the diff is on the bottom and the top
has an arrangement of [commit, status, actions].

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-02 23:23:39 -08:00
David Aguilar 53870294b6 views.mainwindow: Use a larger default window size
The Fibonacci ratio is quite nice.
2009-12-02 23:23:38 -08:00
David Aguilar cc0b70bfa3 doc: Do not mention unused cola.geometry variable
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-02 23:23:38 -08:00
David Aguilar 188bba9f0b doc: Document the 'git cola --classic' option
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-12-02 23:23:38 -08:00
Ben Boeckel 3c5463efb5 Don't ship forked simplejson 2009-12-01 19:15:03 -05:00
David Aguilar c6d6c32edf views.main: Use the clone callback from gitcmds
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-30 01:33:47 -08:00
David Aguilar 73d784605a views.main: Remove unused class attributes
The widget refactor eliminated these.  Less is more.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-30 01:33:02 -08:00
David Aguilar 43a3379bb9 views.main: Use gitcmds in the 'browse branch' callbacks
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-25 21:09:15 -08:00
David Aguilar 596a587847 gitcmds: Add a 'split' parameter to all_refs()
Passing split=True causes all_refs() to return three separate
local_branches, remote_branches, and tags lists.

all_refs() now returns a single concatenated list by default.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-25 21:08:18 -08:00
David Aguilar 848623ca90 models.main: Use the config object for gui.diffcontext.
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-25 21:05:41 -08:00
David Aguilar d35f6087a2 models.main: Remove unused commit message methods
These live in the commands module now.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-25 21:02:03 -08:00
David Aguilar c84566c7bf views.main: Simplify load_template()
It simply signals.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-25 20:49:11 -08:00
David Aguilar d33c10a3b4 qtutils: Death to whitespace
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-25 20:35:21 -08:00
David Aguilar 889b1dd559 gitcmds: Use rstrip() when reading .git/HEAD
Every little bit helps.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-25 20:34:38 -08:00
David Aguilar c78a04f37d commands: Add LoadCommitTemplate
LoadCommitTempate loads the commit message specified in commit.template.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-25 20:34:03 -08:00
David Aguilar b3b84194b2 gitcfg: Fix git-config usage for git 1.6.2
Certain versions of git are sensitive to the order of options
passed to the git-config command.  This ensures that --list
is passed last.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-25 15:51:45 -08:00
David Aguilar a39ba8d9af doc: Mention startup optimizations in the release notes
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-22 15:31:46 -08:00
David Aguilar 52dbb4e3bf doc: Use backticks and rephrase release notes where appropriate
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-22 15:30:05 -08:00
David Aguilar f6869a59c0 gitcmds: Simplify cache usage in current_branch()
current_branch() used a stat cache which was not very reliable.
We now use the raw content of .git/HEAD as the cache key.
This is simpler and more robust than a stat() cache.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-21 20:43:03 -08:00
David Aguilar 599750b4ff gitcfg: Simplify cache usage
The stat cache is probably not very portable and doesn't lend
itself well to testing so bail on it.  We now use the raw
content of each config file as its cache key.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-21 20:41:51 -08:00
David Aguilar 1b8e0e9b7d cola: Make static factory functions consistent in implementation
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-21 20:39:22 -08:00
David Aguilar 9d11d1f545 test: Chain shell() commands
This ensures that all git commands run to completion.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-21 18:55:02 -08:00
David Aguilar 0607bce439 gitcmds: Optimize startup time by adding all_refs()
all_refs() finds tags and branches in one shot.
This avoids two calls to the git command.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-21 18:52:57 -08:00
David Aguilar 087498ac49 models.main: Remove redundant calls to git-config during startup
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-21 13:59:21 -08:00
David Aguilar 6e8f4ab6ff gitcmds: Use st_mtime as the cache key
Otherwise simply reading .git/HEAD invalidates the
current_branch() cache.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-21 13:59:13 -08:00
David Aguilar 17477a2609 gitcfg: Use st_mtime as the cache key
Otherwise the act of reading the configs makes the
stat cache invalid.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-21 05:05:08 -08:00
David Aguilar 6e97c7f2e0 cola: Defer loading the classic view
This removes the classic dock widget.  There's a few reasons
why it's nice to have the classic view in its own window.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-21 04:13:38 -08:00
David Aguilar 49bf148b53 commands: Make model access consistent by using self.model
This is a trivial style cleanup to make the command module
consistent in how it accesses the model object.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-20 21:10:16 -08:00
David Aguilar 171dd0c62a models.main: Remove git_repo_path()
This removes the model's git_repo_path().
Callers now use git.git_path() instead.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-20 21:08:13 -08:00
David Aguilar e3d861df3f gitcmds: Update docstring for untracked_files()
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-20 20:58:54 -08:00
David Aguilar 1f1420ab6d doc: Update git-cola-1.4.1 release notes
Added portability and developer topics to the upcoming
release notes.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-20 20:52:37 -08:00
David Aguilar 6cc9b54f54 test: Add a testcase for gitcmds.merge_message_path()
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-20 20:45:45 -08:00
David Aguilar 039267c574 gitcmds: Factor out abort_merge() and related funcitons
This moves abort_merge(), merge_message_path(), and
merge_message() into the gitcmds module.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-20 20:06:11 -08:00
David Aguilar 2af18c8742 gitcmds: Use git.git_path() to find refs/heads
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-20 19:15:49 -08:00
David Aguilar 942b59fe5b gitcmds: Remove unused reset_helper()
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-20 19:13:24 -08:00
David Aguilar 20bd747b51 test: Add a testcase for gitcmds.tag_list()
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-20 18:42:35 -08:00
David Aguilar 27042f7061 test: Add a testcase for gitcmds.untracked_files()
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-20 17:17:47 -08:00
David Aguilar 545cc79f54 gitcmds: Remove unused 'cola' import
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-20 16:56:05 -08:00
David Aguilar 1deadb4bd8 gitcmds: Simplify default_remote()
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-20 16:49:03 -08:00
David Aguilar 14ce205c7f gitcmds: Remove unused corresponding_remote_ref()
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-20 16:49:02 -08:00
David Aguilar 47303810ed gitcmds: Use the GitConfig object for diff.context
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-20 16:49:02 -08:00
David Aguilar 4a205d6a98 controllers.compare: Handle tracked_branch() returning None
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-20 16:49:02 -08:00
David Aguilar 3a4f350444 commands: Use the GitConfig object for diff.context
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-20 16:48:54 -08:00
David Aguilar ffdb24164c gitcmds: Test and simplify tracked_branch()
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-20 16:45:44 -08:00
David Aguilar bd0d3efc44 test: Add a testcase for default values from GitConfig.get()
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-20 16:09:30 -08:00
David Aguilar fe53a54d7f test: Add a testcase for bool values from GitConfig.get()
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-20 16:07:28 -08:00
David Aguilar 8485311a4f test: Add a testcase for int values from GitConfig.get()
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-20 16:05:57 -08:00
David Aguilar f85b184043 doc: Document the gitcfg module
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-20 16:04:02 -08:00
David Aguilar 9296c491e8 test: Add a test module for the gitcfg module
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-20 16:01:45 -08:00
David Aguilar 39d7157abe gitcfg: Simplify get() by looping over candidate dictionaries
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-20 16:01:31 -08:00
David Aguilar b5856d7e02 gitcmds: Use the static GitConfig instance in default_remote()
This modifies default_remote() to use the static GitConfig instance.
The old version did not always return the right value so this also
adds a test case to ensure correctness.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-20 15:55:42 -08:00
David Aguilar 870f240873 test: Reset the gitcfg instance in GitRepositoryTestCase.setUp()
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-20 15:49:59 -08:00
David Aguilar b20e3e6f4c gitcmds: Use git.git_path() in current_branch()
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-20 15:48:50 -08:00
David Aguilar 9b15b1a083 cola: Add a gitcfg module for interacting with git-config files
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-20 15:48:27 -08:00
David Aguilar 36af28aff6 test: Reset the gitcmd instance in GitRepositoryTestCase.setUp()
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-20 15:44:17 -08:00
David Aguilar 83748ea84a gitcmd: Add a git_path() method for finding .git-relative paths
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-20 15:43:10 -08:00
David Aguilar 465005278b test: Add a test case for gitcmd.branch_list(remote=True)
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-20 11:03:51 -08:00
David Aguilar 446a0bdb47 test: Add a test case for gitcmds.branch_list(remote=False)
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-20 11:00:37 -08:00
David Aguilar c4cc7474aa test: Add a test for the model's 'tags' attribute
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-20 10:59:29 -08:00
David Aguilar 95b976dd4c gitcmd: Use the entire stat object as the cache key in current_branch()
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-20 10:59:13 -08:00
David Aguilar e391197488 models.main: Remove unused 'filename' method
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-20 10:58:31 -08:00
David Aguilar 8dbcb13185 test: Default 'commit' to True in GitRepositoryTestCase
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-20 10:57:32 -08:00
David Aguilar d48857d8e8 views.status: Avoid setHeaderHidden() altogether
We could use it, but the old way of doing it works too.
This simplifies PyQt 4.3 compatibility.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-20 01:57:08 -08:00
David Aguilar 923349b5f1 cola: Support PyQt 4.3
git-cola originally support PyQt 4.3.x.
We lost compatibility with the recent UI rework.
This makes git-cola work on PyQt 4.3.x again by simply
avoiding some of the newer feature.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-20 01:54:27 -08:00
David Aguilar 7f97d2fe74 models.gitrepo: Use a compatibility check for PyQt 4.3.x
This makes the tests pass on older versions of PyQt.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-20 01:54:26 -08:00
David Aguilar c374442e83 gitcmds: Fix for-each-ref usage for git-1.5.x
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-20 01:54:26 -08:00
David Aguilar a82bb01e79 test: Add a test_path() helper for use in git repository tests
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-20 01:54:25 -08:00
David Aguilar 04a5337b1d test: Skip the ColaApplication test when PyQt4 < 4.4
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-20 01:54:25 -08:00
David Aguilar 9567ded3d0 test: Remove embedded utf-8 characters in test_cola_app
Use unichr() instead.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-20 01:54:25 -08:00
David Aguilar c9dee3d3f1 app: Add an instance() function to return the global QApplication
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-20 01:54:25 -08:00
David Aguilar 8ad47e16a5 test: Test the model's 'remotes' attribute
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-19 21:58:35 -08:00
David Aguilar da680f1231 test: Test the model's 'untracked' attribute
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-19 21:58:23 -08:00
David Aguilar c2d466b355 test: Test the model's 'unstaged' attribute
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-19 21:58:08 -08:00
David Aguilar 01647f232a test: Add more docstrings to test_cola_models_main
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-19 21:57:50 -08:00
David Aguilar 48f93bd93e test: Add a test case for the model's 'modified' attribute
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-19 21:51:33 -08:00
David Aguilar d6c423f67c test: Add a test case for the model's 'remote_branches' attribute
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-19 21:51:20 -08:00
David Aguilar 1a674917a3 test: Use GitRepositoryTestCase's setup in all tests
This removes redundant repository initialization code in the tests.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-19 21:45:59 -08:00
David Aguilar b0c8e06ff6 test: Simplify and rename helper.TestCase to helper.GitRepositoryTestCase
The test helper module is now simpler.  We no longer import
join, dirname and basename directly.  Several global variables
were removed.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-19 21:22:35 -08:00
David Aguilar 58e8c86553 test: Use local classes in the serialization test suite
There's no reason to have local test classes in the main test helper.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-19 14:39:34 -08:00
David Aguilar 04de42d4f7 models.compare: Derive from ObservableModel
This modifies the CompareModel and BranchCompareModel classes
to not derive from the main model.  They now derive from
observable model instead.  The update_revision_lists() method
is now part of the CompareModel where it is more appropriate.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-19 14:28:46 -08:00
David Aguilar 6ed329a709 views.main: Use gitcmds.log_helper()
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-19 14:28:46 -08:00
David Aguilar 0bc7d6bd1e gitcmds: Factor out parse_rev_list() and friends
log_helper(), parse_rev_list(), and rev_list_range()
are now part of the gitcmds module.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-19 14:28:38 -08:00
David Aguilar e6a4610c44 gitcmds: Add log_helper() and parse_rev_list()
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-19 14:07:08 -08:00
David Aguilar 1bb2483039 gitcmds: Remove unused upstream_changed_set in worktree_state()
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-19 13:58:30 -08:00
David Aguilar 55b453a896 test: Use assertEqual() in the git ops test
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-19 13:12:57 -08:00
David Aguilar 57f48dcb64 test: Add unit tests for gitcmds.unstage_paths()
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-19 13:12:00 -08:00
David Aguilar 6169bb004d doc: Add git-cola-1.4.1 release notes
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-19 12:10:55 -08:00
David Aguilar da1aedd515 doc: Add github and debian issue URLs to release notes
The release notes now link directly to the respective github and
debian issues closed in each release.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-19 12:10:27 -08:00
David Aguilar fff8c628a5 doc: Document cola.models.browser
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-19 02:15:34 -08:00
David Aguilar 993eb06c6e repobrowser: Use the BrowserModel
This removes all repobrowser data from the main model.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-19 02:10:48 -08:00
David Aguilar 04e1db4b94 models: Add a BrowserModel for use by the repobrowser
This factors out the data used by the repobrowser.
This should allow us to slim down the main model even further.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-19 02:09:49 -08:00
David Aguilar 00f48f852e commands: Fix a bug in the amend interaction
If we had diffed a staged file then cola wouldn't allow the amend
checkbox to take effect.  This was fixed by restricting the cases
where the Diff command is allowed to change modes.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-19 01:38:38 -08:00
David Aguilar 97e4c09491 gitcmds: Factor out worktree_state() and friends
This finally brings the main model down to a manageable size.
The next step is to break out the browser data which is used
by the repobrowser only.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-19 01:34:16 -08:00
David Aguilar d96358fbff commands: Log format-patch output
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-19 01:00:16 -08:00
David Aguilar 62a0ac9aed models.main: Remove unused revert_paths() method
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-19 00:53:43 -08:00
David Aguilar 47fc0ac961 views.main: Remove the amend_is_checked() method
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-19 00:51:18 -08:00
David Aguilar 05b803b9d6 commands: Fix mistyped 'commit'
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-19 00:50:52 -08:00
David Aguilar d31feba27a gitcmds: Factor out format_patchsets()
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-19 00:49:43 -08:00
David Aguilar a88d2fcd7c gitcmds: Factory out diff_helper() and commit_diff()
This continues the work of splitting apart the models.main module.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-18 22:07:11 -08:00
David Aguilar 80578687ff cola.diffparse: Prefix utils.write() with the module name
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-18 22:01:54 -08:00
David Aguilar 88c11a401d models.main: Remove unused diff_details() method
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-18 21:56:29 -08:00
David Aguilar daf633418a models.main: Remove unused add_or_remove() method
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-18 21:44:37 -08:00
David Aguilar 1806dbad74 gitcmds: Use the static git command instance
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-18 21:38:38 -08:00
David Aguilar dd13071bd3 cola: Add a gitcmd module
The gitcmd module provides access to the static git command object.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-18 21:37:39 -08:00
David Aguilar 659b8d8032 models.main: Trivial whitespace fixup when notifying observers
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-18 21:20:47 -08:00
David Aguilar ca528f7b08 gitcmds: Use git-for-each-ref when finding tag names
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-18 21:20:22 -08:00
David Aguilar 1c687fb56e gitcmds: Factor out untracked_files()
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-18 21:04:58 -08:00
David Aguilar 64533fc958 gitcmds: Factor out tracked_branch()
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-18 21:03:52 -08:00
David Aguilar a33a6cc23e gitcmds: Use current_branch() instead of depending on the model
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-18 21:03:06 -08:00
David Aguilar 42f9da1f6c gitcmds: Factor out branch_list()
branch_list() no longer lives within the model.
This version also uses git-for-each-ref, which is preferred over
screen-scraping git-branch.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-18 20:59:25 -08:00
David Aguilar cc6be1306c gitcmds: Factor out and optimize current_branch()
This factors current_branch() out of the main model.
This version is faster than the original in that it no longer
calls out to git.  Instead, we read .git/HEAD directly and
cache the result behind a stat() cache.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-18 20:37:46 -08:00
David Aguilar fd1c14c8fd gitcmds: Factor all_files() out from the main model class
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-18 20:31:07 -08:00
David Aguilar 95e1de309e gitcmds: Add more docstrings
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-18 20:29:46 -08:00
David Aguilar 84e05c1ecd Merge branch 'maint'
* maint:
  views.compare: Add missing button text when comparing commits
  controllers.compare: Fix clashing module and function name
2009-11-18 20:28:22 -08:00
David Aguilar b0a5a9a5a2 views.compare: Add missing button text when comparing commits
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-18 20:27:20 -08:00
David Aguilar a71eb88f68 controllers.compare: Fix clashing module and function name
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-18 20:26:54 -08:00
David Aguilar 23a15165fa Makefile: Add git-cola.app
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-18 19:45:55 -08:00
David Aguilar c2f2d3daaa cola.main: Allow cloning when run outside of a git repository
An open dialog was originally used when cola was run outside of
a git repository.  We now allow cloning a repository as well.

Closes #22

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-17 23:25:39 -08:00
David Aguilar 7d28ffb92f cola.views: Add a StartupDialog for use during application startup
We should give the option of either opening or cloning a repo
when cola is run outside of a git repo.  The StartupDialog
provides the GUI interaction for doing so.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-17 23:25:24 -08:00
David Aguilar e552f0c49c cola: Add a guicmds module for clone_repo()
This factors out the GUI logic for cloning from the main view.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-17 23:24:55 -08:00
David Aguilar 95e2280c66 commands: Make spawning optional in the Clone command
We want to be able to initiate a cola session with a clone so
add the ability to clone without spawning a new session.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-17 23:15:11 -08:00
David Aguilar 362586ce16 MANIFEST.in: Add cola/*.py
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-17 23:12:09 -08:00
David Aguilar 540b4226f0 Merge branch 'maint-1.4.0'
* maint-1.4.0:
  doc: Add git-cola-1.4.0.5 release notes
  doc: Consolidate release notes onto a single page
  cola.commands: Update status tree when amending commits
  views.main: Ensure that the 'amend' checkbox gets unchecked
  cola.utils: Properly launch external apps on win32

Conflicts:
	share/doc/git-cola/relnotes/releases.rst

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-15 21:29:48 -08:00
David Aguilar 77e65d7ca2 doc: Add git-cola-1.4.0.5 release notes
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-15 21:27:59 -08:00
David Aguilar c7cad30c28 doc: Consolidate release notes onto a single page
This makes it simpler to maintain release notes.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-15 21:24:06 -08:00
David Aguilar b7ebc165b0 cola.commands: Update status tree when amending commits
The status tree was not getting updated when commits were
amended.  This was because the Commit command was not resetting
the model's 'head' attribute.

We now derive Commit from ResetMode so that this and other
duplicate actions are handled.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-15 21:23:44 -08:00
David Aguilar 3873ececd3 views.main: Ensure that the 'amend' checkbox gets unchecked
The 'amend' checkbox was not always unchecking itself when
mode were switched by clicking on the empty area in the status
tree.  We now handle this condition correctly.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-15 21:13:51 -08:00
David Aguilar 31c6e7342c cola.utils: Properly launch external apps on win32
Windows requires an amazing number of hacks to do something
as simple as running a command in the background.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-15 20:49:29 -08:00
David Aguilar 21b3b0efd0 doc: Consolidate release notes onto a single page
This makes it simpler to maintain release notes.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-14 10:58:45 -08:00
David Aguilar 211e3ccb86 Merge branch 'maint-1.4.0'
* maint-1.4.0:
  doc: Add git-cola-1.4.0.4 release notes
  Move SSH_ASKPASS scripts to share/git-cola/bin
2009-11-14 04:05:51 -08:00
David Aguilar a4abb434f6 doc: Add git-cola-1.4.0.4 release notes
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-14 04:03:09 -08:00
David Aguilar 3a26c5cee0 Move SSH_ASKPASS scripts to share/git-cola/bin
This fixes Lintian warnings by removing the non-standard
/usr/libexec directory.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-14 04:03:01 -08:00
David Aguilar 8eb1ce6f73 Merge branch 'maint-1.4.0'
* maint-1.4.0:
  doc: Add git-cola-1.4.0.3 release notes
  views.status: Eliminate X11 warnings
2009-11-09 16:04:41 -08:00
David Aguilar c922a3390c doc: Add git-cola-1.4.0.3 release notes
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-09 16:03:47 -08:00
David Aguilar 8d59660cf9 views.status: Eliminate X11 warnings
Expanding childless top-level items causes X11 warnings to appear
in the shell.  This was happening because we were looping over
each top-level item inside of expand_items().

expand_items() now expands the item passed into it only.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-09 14:57:25 -08:00
David Aguilar 50b82b127e docs: Use 'Git GUI' consistently
There were several places where we were using the phrase 'git gui'
instead of 'Git GUI'.  The latter is preferred.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-05 20:16:53 -08:00
David Aguilar 003e3081d3 Merge branch 'maint-1.4.0' 2009-11-05 18:45:24 -08:00
David Aguilar 7ad83e82d0 doc: Add git-cola-1.4.0.2 release notes
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-05 18:45:05 -08:00
David Aguilar 259b150f91 models.main: Guard against non-int font sizes
The cola.fontdiff setting can contain non-integer
font sizes, so guard against it by casting to a float
before casting to int.

Closes #32

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-05 18:45:05 -08:00
David Aguilar ee17ea12d8 views.main: Show 'Exit Diff Mode' when using Diff->Expression
We weren't showing the 'Exit Diff Mode' button when going into
the 'Diff->Expression' mode.

Closes #31

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-11-05 18:45:05 -08:00
David Aguilar fd851a6bc9 views.main: Add a dropEvent handler for drag+drop access to git-am
It is now possible to apply patches by dropping them onto cola.
cola also allows you to drop directories in which case cola
walks the directory structure gathering a list of patches, sorts
them alphanumerically, and applies them in order.

Closes #3

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-31 15:34:29 -07:00
David Aguilar d52775e800 commands: Add an ApplyPatches command
This will be used to implement drag+drop access to git-am.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-31 15:34:29 -07:00
David Aguilar c1ac1474ed cola.signals: Do not depend on QtCore
The signal names are plain strings now and thus there's no
reason to pass them through QtCore.SIGNAL().  This simplifies
the signals module by making it contain nothing more than a
collection of constants.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-31 15:34:29 -07:00
David Aguilar ad3437453c Makefile: 'test' should depend on 'all'
This ensures that the translation files have been built.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-31 15:34:03 -07:00
David Aguilar 22355e51d3 doc: Add git-cola-1.4.0.1 release notes
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-31 15:17:25 -07:00
David Aguilar e40455e374 views.main: Fix a typo when loading merge commit messages
self.merge_message_hash was mistyped as self.merge_msg_hash.

Closes #30

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-31 15:00:10 -07:00
David Aguilar 1ef741d682 controllers.stash: Add missing cola.signals import
We forgot to import the signals module during the last
round of refactoring.

Closes #29

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-31 14:59:49 -07:00
David Aguilar 752124b7de Makefile: Remove build files during 'make clean' only
We used to remove 'build', 'dist', and builtin_version.py in the
build targets.  We no longer do any cleaning in the build
targets and handle all cleanup tasks in the 'clean' target only.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-31 14:26:17 -07:00
David Aguilar ceb0b4392e trivial: Remove trailing whitespace in cola.gitcola
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-31 14:16:37 -07:00
David Aguilar 759a54c574 cola.version: Fix builtin_version usage
We no longer remote builtin_version in setup.py.
This allows us to find builtin_version when running cola from
its source tree.  We also break out of the loop when
builtin_version is found which makes cola return the correct
version number instead of always falling back to 'git describe'.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-31 13:19:17 -07:00
David Aguilar d807842cc6 views.status: Allow staging untracked files
We were emitting modified files when we should have
emitted unstaged files.

Closes #27

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-28 14:52:55 -07:00
David Aguilar db93f8d3f2 models.main: Sort results when querying for all files
'git ls-files --all --others' can return files in an unsorted order,
namely untracked files can come before tracked files.
Return a sorted list in everything() to make it more intuitive.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-27 12:35:50 -07:00
David Aguilar 28fc575c92 COPYRIGHT: It's time to party like it's 2009
Signed-off-by: David Aguilar <david.aguilar@disneyanimation.com>
2009-10-27 11:23:03 -07:00
David Aguilar 8c9dffa525 MANIFEST.in: Add Sphinx documentation
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-24 12:34:55 -07:00
David Aguilar 1c99b94fb3 models.main: Handle trailing NULL in ls-files output
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-24 01:12:39 -07:00
David Aguilar 8011fbe2da doc: Add git-cola-1.4.0 release notes
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-24 01:11:56 -07:00
David Aguilar d20a072f84 doc: Fix header in git-cola-1.3.7 release notes
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-24 01:07:07 -07:00
David Aguilar 696899f3f8 doc: Add git-cola-1.3.9 release notes
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-24 01:06:05 -07:00
David Aguilar e27124b155 doc: Add git-cola-1.3.8 release notes
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-24 01:03:44 -07:00
David Aguilar a007873d5e doc: Add git-cola-1.3.7 release notes
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-24 01:00:40 -07:00
David Aguilar 307b734f8c doc: Add git-cola-1.3.6 release notes
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-24 00:49:41 -07:00
David Aguilar c5b5519498 models.main: Honor .gitignore
We forgot to pass --exclude-standard to git-ls-files.
An untracked_files() method was added and update_status() was
updated to use it.

Closes #26

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-20 02:19:42 -07:00
David Aguilar 5fe0bfdf6a models.main: Optimize the everything() method
We don't need to call strip() if we can omit the last character.
We don't need to sort files since users might expect git's
ignore-dot-files order.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-20 02:18:58 -07:00
David Aguilar d42b2d4cc5 win32: Update installer script to use 'git-cola'
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-19 00:14:19 -07:00
David Aguilar 56dcadb900 cola.version: Pass HEAD to git-describe
There's some discussions on the git ML about changing
git-describe so that it automatically appends -dirty.
This future proofs us in case this patch goes through.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-16 15:35:14 -07:00
David Aguilar 950a41bfc3 setup.py: Generate builtin_version.py in git only
The build system always removed builtin_version.py.
When distributing tarballs, though, we don't have .git
and thus can't generate this file during the build.

This changes things so that the builtin_version.py
module is only generated and deleted when inside
of a git repository.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-14 04:58:29 -07:00
David Aguilar 2b7b1bfd2c Makefile: install-doc should not depend on install-html
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-14 04:14:35 -07:00
David Aguilar 52ed93ec90 .gitignore: Add an entry for cola.tuxfamily.org
This directory is used as a staging ground for the next release.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-14 03:45:45 -07:00
David Aguilar f76e41111c Makefile: Simplify $DESTDIR handling
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-14 01:07:59 -07:00
David Aguilar a8f003e375 .gitignore: Add an entry for Meta/
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-14 01:07:41 -07:00
David Aguilar feb7548c3d setup.py: Add ssh-askpass-darwin
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-11 17:04:07 -07:00
David Aguilar 1d8fb7871b cola.version: Pass --match when using git-describe
This ensures that we only find tags matching the v$VERSION
major.minor.micro.patch naming convention.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-11 17:00:22 -07:00
David Aguilar 4fcf9d2d3f tests: Remove the need for test/Makefile
The main Makefile now has the logic that previously lived in
test/Makefile.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-10 06:35:41 -07:00
David Aguilar a0c22b7be1 tests: Remove obsolete message observer test case
We don't validate message names anymore so there's no need to
test the non-existant validator.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-10 06:34:48 -07:00
David Aguilar 393a3bb565 Add ssh-askpass-darwin to MANIFEST.in
Signed-off-by: David Aguilar <david.aguilar@disneyanimation.com>
2009-10-09 10:25:54 -07:00
David Aguilar ff2c607fbc win32: Add libexec/ entries to the installer
libexec/ is a new directory so we need to teach the win32
installer about it.

Signed-off-by: David Aguilar <david.aguilar@disneyanimation.com>
2009-10-09 10:24:42 -07:00
David Aguilar eb60f99291 Provide an ssh-askpass implementation for Darwin/OSX
The fallback tcl/tk script hangs on Mac OSX.  This one works great.

Closes #23

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-08 23:16:21 -07:00
David Aguilar 47b1741cc3 Add an ssh-askpass implementation
cola locks up when remote operations are attempted
on SSH URLs that require a password and no TTY is
available.

To fix this we now provide our own SSH_ASKPASS
implementation as a fallback.

Closes #23

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-08 20:20:09 -07:00
David Aguilar 6b2e6aab97 models.gitrepo: Ignore unknown paths
PyQt4 segfaults when children are added to QStandardItemModels
after a view is observing it.

This works on older version of PyQt4 so this is a workaround
until we have a better solution.

The downside of this change is that we won't recognize when
new files appear in the repository.  The upside is that it
avoids PyQt4 segfaults.

Closes #25

Signed-off-by: David Aguilar <david.aguilar@disneyanimation.com>
2009-10-08 14:45:03 -07:00
David Aguilar d607171708 models.main: Do not return cached values in everything()
model.everything() used to use its cached values for staged,
unstaged, etc.  It's better to simply re-run git ls-files instead.

Signed-off-by: David Aguilar <david.aguilar@disneyanimation.com>
2009-10-08 14:40:49 -07:00
David Aguilar 5244cb2e6e cola.observable: Simplify the base model
We no longer need to register message names.  This takes away the
runtime check for valid message names, but in practice we won't
ever use a bad name since we're already using the class constant.
Thus, the runtime check is useless.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-08 00:10:07 -07:00
David Aguilar db1b02ac34 models.main: Avoid using the built-in 'file' identifier
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-08 00:10:07 -07:00
David Aguilar a11032c14f doc: Add gitcmds and squash documentation warnings
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-08 00:10:07 -07:00
David Aguilar 9396694d4e doc: Add gitcmds.rst
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-08 00:10:07 -07:00
David Aguilar c87758027a Makefile: Tidy up the 'clean' target
The doc Makefile should call Makefile.asciidoc's 'clean' target.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-07 23:50:01 -07:00
David Aguilar 2234cb29ea models.gitrepo: Use a custom event type for GitRepoInfoEvents
We now give GitRepoInfoEvents a custom type() so that we can
tell them apart from builtin Qt events.

Closes #25

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-07 21:05:31 -07:00
David Aguilar 18ada5e7a1 views.main: Add a dialog for creating tags
The 'Actions/Create Tag...' menu now launches a dialog for
creating tags.  We assume that users have a gpg-agent setup
when using signed tags.  If that is not the case then
git will read the GPG password from stdin.

Closes #11

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-07 00:59:10 -07:00
David Aguilar c36343f781 views.mainwindow: Re-arrange menus to accomodate 'Create Tag...'
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-07 00:53:03 -07:00
David Aguilar f80724a3df models: Add a TagModel class
This adds a simple data container for use by the tag dialog.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-07 00:51:11 -07:00
David Aguilar ababb80099 views: Add a CreateTag dialog
This adds a dialog for creating tag objects.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-07 00:50:24 -07:00
David Aguilar c4cd0b8625 commands: Add a Tag command class
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-07 00:49:00 -07:00
David Aguilar 57f41902f5 models.main: Optimize stage_paths(), unstage_paths(), and revert_paths()
We can do less in each function since nothing is observing the
paths_staged/unstaged/reverted messages anymore.

stage_paths() now benefits from only ever running at most 2 git commands
to update the index - add for additions and add -u for removals.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-07 00:48:24 -07:00
David Aguilar 36b8183822 notification: Replace QObject usage with a pure-Python implementation
Qt signals+slots are limiting because they don't allow optional
keyword arguments.  Let's write our own so we can simplify the
Diff command later.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-07 00:38:09 -07:00
David Aguilar 901f4d33e7 git: Make the git command class thread-safe
This uses threading.Lock to serialize access to the git binary.
Git calls fail when another git process is running due to the
.git/index.lock file.  By serializing git calls we guard against
threads triggering this condition.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-06 23:32:12 -07:00
David Aguilar 43d5ec1f10 qobserver: Handle editable combo boxes in 'SLOT()'
We used to avoid using Qt's data so that we'd always index into
the model directly.  It's simpler to use it though, and also handles
the editable combobox case.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-06 22:22:45 -07:00
David Aguilar fa1efe999e models.main: Consistency cleanup to avoid double underscores
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-06 22:21:08 -07:00
David Aguilar 1636c72c6d controllers.merge: Default to False when prompting to abort a merge
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-06 22:20:38 -07:00
David Aguilar 8b0f2a708e views.main: Define a foreground color whenever a background color is defined
We were setting the background color but weren't setting the
foreground color.  This makes the column indicator look bad when
a dark theme with a light font is used.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-05 00:33:33 -07:00
David Aguilar 9342356f70 views: Remove package-scope imports
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-05 00:25:18 -07:00
David Aguilar e675f76e7f Death to .ui: Remove the last .ui files
This eliminates the cola.gui namespace and removes the pyuic4 dependency.

Closes #24

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-05 00:05:58 -07:00
David Aguilar 3809fdd5e3 Death to .ui: Remove .ui file support from the build system
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-05 00:05:53 -07:00
David Aguilar 41c3b3de0d Death to .ui: Remove 'create_standard_view()'
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-04 23:50:42 -07:00
David Aguilar 9ffad717a3 Death to .ui: Update 'SearchView' to remove its .ui dependency
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-04 23:49:54 -07:00
David Aguilar d6b7b73175 Death to .ui: Remove remote.ui
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-04 22:49:10 -07:00
David Aguilar c1fd976101 Death to .ui: Add a 'RemoteView' class
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-04 22:49:05 -07:00
David Aguilar 148c14d6cc Death to .ui: Remove items.ui
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-04 16:07:14 -07:00
David Aguilar 74c850abc9 Death to .ui: Add a 'ListView' class
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-04 16:07:04 -07:00
David Aguilar 8f9ad1b61f Death to .ui: Fix a typo in views.compare
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-04 16:06:16 -07:00
David Aguilar 061a8eba0b Death to .ui: Remove stash.ui
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-04 15:48:21 -07:00
David Aguilar d08bb2ee1d Death to .ui: Add a 'StashView' class 2009-10-04 15:48:00 -07:00
David Aguilar a5fdd3761e views.status: Derive from QWidget, not QDialog
Otherwise, hitting ESC closes the widget!

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-04 15:23:06 -07:00
David Aguilar a1574da739 Death to .ui: Remove createbranch.ui
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-04 15:21:30 -07:00
David Aguilar ebdeb76256 Death to .ui: Add a 'CreateBranchView' class
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-04 15:21:19 -07:00
David Aguilar 5a5d844c08 Death to .ui: Remove combo.ui
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-04 07:43:25 -07:00
David Aguilar 486daf6e75 Death to .ui: Add a 'ComboView' class
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-04 07:43:18 -07:00
David Aguilar c17d3e620a Death to .ui: Remove compare.ui
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-04 07:43:18 -07:00
David Aguilar d2d537f932 Death to .ui: Add a 'CompareView' class
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-04 07:43:07 -07:00
David Aguilar bf6b73ce9d Death to .ui: Remove branchview.ui
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-04 00:06:11 -07:00
David Aguilar 572286f993 Death to .ui: Add a 'BranchCompareView' class
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-04 00:05:54 -07:00
David Aguilar e8ef7a9ab2 Death to .ui: Remove bookmark.ui
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-03 19:03:30 -07:00
David Aguilar 8259559368 Death to .ui: Add a 'BookmarkView' class
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-03 19:03:12 -07:00
David Aguilar 596f81b8ec Death to .ui: Remove options.ui
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-03 18:41:04 -07:00
David Aguilar dcd40e0435 Death to .ui: Add an 'OptionsView' class to replace the .ui file
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-03 18:40:36 -07:00
David Aguilar 138d73906b Death to .ui: Add a StandardDialog class
We'll need this to remove the rest of the .ui files.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-03 17:36:17 -07:00
David Aguilar 04b4e1a412 Death to .ui: Remove the unused about.ui file
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-03 16:44:52 -07:00
David Aguilar cea2ee84e9 views.about: Death to .ui files
This removes the .ui file dependency for the 'About' dialog.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-03 16:43:57 -07:00
David Aguilar 2e5c95a7a6 views.main: Add an 'Unstage' button to the 'Actions' dock widget
This provides a way to unstage files that doesn't involve double-clicks
or context menus.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-03 16:00:50 -07:00
David Aguilar a1066d510a views.main: Activate the 'Unstage Files From Commit' menu action
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-03 15:59:28 -07:00
David Aguilar 15b4d608b7 commands: Add an 'UnstageSelected' command
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-03 15:57:03 -07:00
David Aguilar d1f7b7c045 cola: Integrate the SelectionModel
Selection is now handled by a static SelectionModel instance.
This allows individual tools to modify the selection and have
the selection reflected in the corresponding menus and actions.

Closes #19

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-03 15:22:02 -07:00
David Aguilar 6d470934ce __init__: Add convenient accessors for the selection model
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-03 15:18:13 -07:00
David Aguilar 30fd85abf2 Add a SelectionModel for synchronizing selection across views
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-03 15:16:39 -07:00
David Aguilar fb6a87155e views.main: Remove unused 'selection()' method
This is preparation for the next series of commits which will
move information about which filenames have been selected into
a standalone selection model.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-03 14:30:01 -07:00
David Aguilar 9ed3b35ebb models.main: Propery detect files that have been changed upstream
We weren't using the merge-base which leads to false positives.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-03 14:17:31 -07:00
David Aguilar 97f9a39e32 models.main: Move 'diff_filenames()' into the gitcmds module
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-03 14:10:13 -07:00
David Aguilar c7202246e0 models.main: Calculate current/tracked branch earlier on
These values are used to calculate branches that are changed upstream
so set them before gathering that information.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-03 14:04:52 -07:00
David Aguilar 76b86ecb91 models.main: Move 'corresponding_remote_ref()' into the gitcmds module
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-03 13:57:28 -07:00
David Aguilar 5f526d5efe cola classic: Speed up the GUI by only diffing files with status
We were blindly emitting 'diff' signals, even if the file was
uninteresting.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-03 13:43:19 -07:00
David Aguilar d2c5a35f61 cola classic: Issue Diff commands when clicking
This modifies the classic view to issue diff commands as
filenames are selected.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-03 13:23:38 -07:00
David Aguilar 5cced30bc3 models.base: Remove an outdated TODO note
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-03 13:23:38 -07:00
David Aguilar 883c7e7cc0 controllers.main: Removed unused imports and refactored shutdown code
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-03 13:23:38 -07:00
David Aguilar 1cab89db16 cola classic: Use the 'updated' message only to infer model changes
The model used to emit special signals that the classic view watched.
The GitRepoModel now observes the main cola model and keeps track of
of its own state.  This will allow us to eliminate the specialized
signals which are currently used by the classic view only.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-02 04:16:09 -07:00
David Aguilar 85ba75889e cola classic: Use the cola notifier in the repo tree view
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-02 04:13:08 -07:00
David Aguilar ff433d9736 cola.cmdfactory: Enable undo/redo to allow AmendMode to recall messages
AmendMode knows how to recall the previous commit message by inspecting
the undo/redo stack.  This re-enables the stacks to enable this feature.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-02 01:38:36 -07:00
David Aguilar fcfeeda426 cola classic: Minimize memory usage
We were carrying around references to GitRepoEntry instances
when we could have looked them up in the GitRepoEntryManager.
This removes the redundant references to minimize memory usage.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-02 01:25:56 -07:00
David Aguilar 814b7e0692 controllers.main: Remove redundant call to update_tab_width()
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-02 01:16:38 -07:00
David Aguilar bfb661691d cola.git: Raise ValueError when given bad values in shell_quote
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-10-01 16:54:59 -07:00
David Aguilar 331918c7b9 models.gitrepo: Use uppercase constant names for GitRepoSignals
We now use non-Qt signatures in the signals used for thread communication.
The signal defines were made into upper case variable names to better
signify that they are constants.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-29 14:38:51 -07:00
David Aguilar 1a8b27bd62 inotify: Hide the 'Rescan' button when inotify is enabled
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-25 02:55:53 -07:00
David Aguilar 629114eca4 inotify: Factor inotify thread management out of the main controller
The inotify threads are now started outside of the controllers in
main().  This will enable the standalone classic view to take
advantage of inotify.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-25 02:55:09 -07:00
David Aguilar aa76b81b7c main: defer command registration until after the cwd has been set
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-25 02:30:13 -07:00
David Aguilar 687420e7eb cmdfactory: Disable the undo stack until the GUI component is ready
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-25 02:28:55 -07:00
David Aguilar a35aede305 controllers.main: Add missing import
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-25 02:24:23 -07:00
David Aguilar 0475e7f2da cmdfactory: Factor out the qtutils dependency
The qtutils dependency broke the build because we were
requiring UI components in core.  The UI listener now
registers itself at runtime.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-25 02:10:59 -07:00
David Aguilar 40ab16803d controllers.classic: Use the static model instance
There was a .pyc laying around that prevented me from seeing
this problem.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-25 00:01:13 -07:00
David Aguilar 6f7a9487ca views.main: Bring back the 'Stage Changed' button
It says 'Stage' now, though, and stages everything if there's
nothing selected.  Otherwise, it stages the selection.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-24 03:47:31 -07:00
David Aguilar ee35e75cb0 commands: Add a Mergetool command
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-24 03:36:11 -07:00
David Aguilar 7b5ea063f1 README: Update the bugtracker blurb
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-24 03:08:04 -07:00
David Aguilar 81bf2e019f README: Link to the git-cola homepage
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-24 03:02:42 -07:00
David Aguilar b8757e6594 classic: Use the notifier to run commands
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-24 02:54:49 -07:00
David Aguilar e4e97d7cb2 commands: Update the visualize history commands
We shouldn't use 'sh -c'.  This probably was a win32 workaround,
so we'll revisit this later.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-24 02:54:27 -07:00
David Aguilar f82313992f commands: Remove the need to implement is_undoable() in subclasses
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-24 02:17:53 -07:00
David Aguilar 9054d821f7 commands: Add a Difftool command object
Difftool implements a command object for running git-difftool.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-24 02:08:48 -07:00
David Aguilar 2552cf916d views.status: Include the staged mode in the difftool signal
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-24 02:08:06 -07:00
David Aguilar 754bf807f9 controllers.main: Remove unused edit_file() method
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-24 02:07:33 -07:00
David Aguilar 018fbdd0db views.status: Restore selection on rescan
This makes it so that the status tree's selection is preserved
across the 'updated' callback.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-24 01:50:22 -07:00
David Aguilar d7aafa6f36 commands: Don't set 'filename' in Diff
The Command base class already sets 'filename' so don't set it again.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-24 01:49:24 -07:00
David Aguilar 13eaa396dc controllers.main: Remove more unused methods
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-24 01:48:21 -07:00
David Aguilar 9573057eb1 views.main: Restore the scrollbar value when updating
This keeps the view at the current location when using
interactive diff staging.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-24 01:47:57 -07:00
David Aguilar 905aeaf904 doc: Add the diffparse module to the sphinx documentation
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-24 01:46:35 -07:00
David Aguilar f108e10a95 README: Mention the mailing list and bug tracker in the README
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-24 01:46:08 -07:00
David Aguilar c8c874a7fe commands: Re-run a diff when processing diff selections
Re-run the diff when processing diff selections so that we
get immediate feedback about the effect of the command.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-24 00:55:01 -07:00
David Aguilar 0ac5e259f7 views.status: Fix the double-click callback
The file names are lists, not single values.
The command object expects lists, so pass them through as-is.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-24 00:53:46 -07:00
David Aguilar 61f73f5aa4 commands: Add a CheckoutBranch command and use it
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-24 00:27:05 -07:00
David Aguilar b0004646fc controllers.options: Fix the interactive tab width control
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-24 00:02:17 -07:00
David Aguilar 8e679265a0 commands: Add a DeleteBranch command
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 23:47:09 -07:00
David Aguilar f46c61b71f qtutils: Log using the static notifier
Log is now implemented in terms of notification.
This means that logging can be handled independently of the GUI.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 23:37:50 -07:00
David Aguilar f5f008fc10 commands: Add logging to the commit and staging commands
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 23:31:37 -07:00
David Aguilar 96267246cb views.main: Move the branch menu actions into the main view
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 23:18:08 -07:00
David Aguilar c5ff56e23b controllers.repobrowser: Simplify select_file_from_repo()
This removes the need to pass 'model' and 'parent' to select_file_from_repo().

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 20:42:09 -07:00
David Aguilar d8978407fc views.ListView: Guard against None parents
Qt might not have an active window and thus widget.parent()
returns None.  We now gracefully handle this scenario.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 20:41:06 -07:00
David Aguilar 67a3e1ff9d controllers.util: Remove the need to pass 'parent' to choose_from_list()
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 20:16:10 -07:00
David Aguilar 346a25be44 views.standard: Remove the 'parent_view' attribute
This field is useless now that the views are decoupled and
communicate using the notifier.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 20:15:07 -07:00
David Aguilar fc4f212efd controllers.util: Remove the need to pass 'parent' to choose_from_combo()
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 19:50:33 -07:00
David Aguilar 393f063f2c views.main: Add callbacks for the repository browser
This moves the repo browser callbacks into the main view,
further minimizing the role of the main controller.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 19:43:16 -07:00
David Aguilar 35e0635f3d controllers.repobrowser: Import QtGui only instead of each class
This changes the imports so that we only import QtGui and use the
classes contained within instead of importing QDialog and QMenu
directly.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 19:37:25 -07:00
David Aguilar 16bd4306a8 models.main: Fix the tests for {stage,unstage,revert}_paths()
The old code would silently call update_status() so that its view
of the world was up-to-date before issuing git commands.

The tests check for this behavior, so reinstate the original
behavior from the classic model.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 18:22:02 -07:00
David Aguilar 4781fde8e2 tests: Fix the models.observable test cases
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 18:19:49 -07:00
David Aguilar 291f31f2cd commands: Clear the diff display when running the Commit command
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 17:44:11 -07:00
David Aguilar 5eb1b5c3fa views.main: Handle cherry picking and patch exporting in the main view
Consequently, the commit browser moved as well.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 17:41:50 -07:00
David Aguilar 87fe058e15 signals: Add 'cherry_pick' and 'format_patch' signals
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 17:40:49 -07:00
David Aguilar 5413c73e3d commands: Add a FormatPatch command
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 17:39:58 -07:00
David Aguilar 2130f0ad27 commands: Add a CherryPick command
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 17:39:43 -07:00
David Aguilar e79fa8f552 controllers.selectcommits: Remove the need to pass in a model and view
We now default to using the static model instance so there's no
need for callers to supply the model.  Additionally, instead of
passing in the view we use Qt's active window.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 17:10:59 -07:00
David Aguilar 00dcfa41fb views.main: Move bookmark callbacks into the main view
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 16:58:49 -07:00
David Aguilar e148196edb views.main: Implement 'clone' and 'open' using editor commands
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 04:11:30 -07:00
David Aguilar f0b54a82ae commands: Add VisualizeAll and VisualizeCurrent commands
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 03:48:08 -07:00
David Aguilar 632bf2f75b views.main: Add titles to the commit error messages
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 03:25:41 -07:00
David Aguilar c6da4234e7 views.main: Handle the commit comparison menu actions
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 03:18:45 -07:00
David Aguilar 6ff79b1540 commands: Add a StageModified editor command
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 03:11:58 -07:00
David Aguilar bd48484d92 commands: Add a StageUntracked command object
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 03:09:09 -07:00
David Aguilar a89716b462 views.main: Show the current row in a human-friendly way
Count rows starting from 1 instead of 0 like a computer.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 03:01:58 -07:00
David Aguilar 5d95762477 cola.commands: Add an UnstageAll
The unstage-all command resets the git index.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 03:00:29 -07:00
David Aguilar eabd0af733 views.mainwindow: Remove redundant menu entries for cola-classic
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 02:55:05 -07:00
David Aguilar 41277f217a views.main: Handle the help menu in the main view
This removes the help menu callbacks from the main controller.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 02:51:23 -07:00
David Aguilar 351d778d36 views.main: Move the options callback into the main view
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 02:42:07 -07:00
David Aguilar 1082c7f76e views.main: Move merge/abort callbacks into the main view
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 02:37:55 -07:00
David Aguilar ea8f50fc4b views.main: Handle the Edit menu
This removes the edit menu handling from the main controller.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 02:29:27 -07:00
David Aguilar 3983920549 views.main: Handle the CompareBranches action
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 02:25:08 -07:00
David Aguilar 07bcdc2101 commands: Add a missing import for the DiffParser
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 02:24:43 -07:00
David Aguilar 5892501380 search: Move the search callback handler into the main view
This is more remove-code-from-the-main-controller work.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 02:01:51 -07:00
David Aguilar b8d1fb235f commands: Move the delete-files code into a command
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 01:48:02 -07:00
David Aguilar e5cd420884 views.main: Move the stash callback into the main view
This removes the callback handler from the controller.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 01:28:52 -07:00
David Aguilar cea878b757 views.main: Use the ApplyDiffSelection command
This moves behavior from the view into a command where it belongs.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 01:16:17 -07:00
David Aguilar 9221de6e39 commands: Add an ApplyDiffSelection command
This command applies patches and patch-subsets.
This implements the visual patch selection backbone.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 01:15:22 -07:00
David Aguilar 3002e39456 commands: Add command implementations
This libifies many of the operations cola was performing
in the main controller.  We'll keep gutting controllers
until we have commands for everything.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 00:17:30 -07:00
David Aguilar f676dedff1 cola: Remove DiffParser from utils
It's in cola.diffparse now.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 00:16:03 -07:00
David Aguilar ecdabb6521 controllers.main: Remove the status_tree and other UI work
The main controller is starting to get twisted apart.
This removes a good portion of the duties the controller
was performing.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 00:15:40 -07:00
David Aguilar 1849a8f0fd views.main: Take all UI code from the main controller
The philosophy is that views should broadcast signals for
the command factory to interpret.  This moves all of the
user interaction code in the view module where it belongs.

The corresponding commands are in subsequent commits.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 00:14:37 -07:00
David Aguilar 4b6fee0597 cola.main: Scan for changes outside of the Qt loop
Bootstrapping happens outside of the Qt event loop
since that'll be removed from the main controller in the
next series of commits.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 00:13:16 -07:00
David Aguilar 7de60cffd4 models.main: Remove add_signoff, add getcwd()
add_signoff is becoming a command.
getcwd() is remembers chosen directories for next use.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 00:11:47 -07:00
David Aguilar 303dbb52b4 views.status: Generate more signals in the status tree
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 00:10:43 -07:00
David Aguilar c1cfd5674e cola: Add diffparse.DiffParser
This helps break the utils module apart.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 00:09:47 -07:00
David Aguilar ddc577a1dc views.mainwindow: Simplify the main ui code further
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 00:08:41 -07:00
David Aguilar 3ba6d9d331 controllers.remote: Use the static model instance
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 00:07:20 -07:00
David Aguilar a1ee759dba notification: Add a debugging environment variable
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 00:06:36 -07:00
David Aguilar 757b837658 observable: Update docstrings to show better examples
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 00:05:49 -07:00
David Aguilar 42a191567b commands: Add an AddSignoff command
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 00:04:34 -07:00
David Aguilar a771d52b0e commands: Handle common model params in the base Command class
This adds handling for diff_text, mode and head to the base
command class.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-23 00:00:49 -07:00
David Aguilar c3dde8a855 signals: Add more signals used by cola
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-22 23:56:57 -07:00
David Aguilar 182a983d6c cola: Remove the 'classic' model
This updates all tests and documentation for the classic model
removal.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-22 23:56:04 -07:00
David Aguilar 1b1517845d classic view: Use the static main model instance
This removes the dependency on the classic model.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-22 23:54:59 -07:00
David Aguilar 94982ce8d9 cmdfactory: Observe model parameters
The cmdfactory converts model notifications into messages
that are picked up by the Qt observers.  This adds more
messages to its notification list.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-22 23:52:57 -07:00
David Aguilar 0baac33b78 models.main: Absorb the classic model
This makes things more convenient for now.
This files still needs to be broken apart into models.util.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-22 23:47:50 -07:00
David Aguilar a274eecd0a cmdfactory: Add docstrings
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-22 23:33:17 -07:00
David Aguilar 70f1c3d0d5 utils: Add checksum() and quote_repopath()
checksum() returns an md5 hash for use on commit messages.
quote_repopath() works around shell behavior on win32.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-22 22:14:28 -07:00
David Aguilar c3e86d20d7 views.main: Set a minimum height on the auxillary button
This fixes a problem where the layout wasn't showing the entire
contents of the "exit <mode> mode" button.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-19 16:00:47 -07:00
David Aguilar cf9a03e45e views.mainwindow: Move signoff and commit buttons in the editor
This moves the signoff and commit buttons into the commit message
editor dock.  This streamlines the UI so that all commit message
controls are in the same visual space.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-19 15:37:23 -07:00
David Aguilar 3e7a8999df views.main: Show row along with column in the text-position display
This moves the text-position display out of the actions button dock
and into the commit message editor.  The display also gives warnings
when lines are too long; yellow, orange and red backgrounds are used
at the 64, 72, and 78 character-wide boundaries.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-19 15:37:23 -07:00
David Aguilar e5a4a236e6 views.main: Replace amend radio buttons with a checkbox
This uses less screen real-estate.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-19 15:37:23 -07:00
David Aguilar f8564e6116 qtutils: Make SLOT take optional arguments
If callers do not pass in any values when SLOT is called then we
relay the values passed in by the callback invoker.  When values
are provided then we prefer those values over the ones available
when the callback is invoked.

This makes it so that values provided at registration-time
override run-time values from the signal.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-19 15:37:23 -07:00
David Aguilar c0a2a57adf controllers.main: Remove handling for the amend UI elements
UI interaction is directly handled by the view and
model state management is handled by the command object
so don't do anything in the controller.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-19 15:37:23 -07:00
David Aguilar e98dc7c470 __init__: Export the command factory module's factory() method
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-19 15:37:23 -07:00
David Aguilar 744c9abbfc views.main: Respond to 'amend' notifications
This register signals to handle the amend UI elements.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-19 15:37:23 -07:00
David Aguilar 2b505db6bb commands: Add an 'AmendMode' command
AmendMode attempts to enter amend-commit mode.
Commits are amended when in this mode.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-19 15:37:16 -07:00
David Aguilar 12899cf139 views.mainwindow: Remove the 'Load Previous Commit Message' action
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-19 13:48:43 -07:00
David Aguilar 8998db543c signals: Add amend, edit, information, and log_text signals
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-19 13:46:19 -07:00
David Aguilar cdd3daa511 commands: Make commands undoable by default
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-19 13:45:20 -07:00
David Aguilar f445a31a65 cmdfactory: Do not register commands in the factory
This removes the command registration from the factory.
Registration is now done by the cola.commands module in main().

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-19 13:45:12 -07:00
David Aguilar 90dd7199de cmdfactory: Observe the 'current_text' model attribute
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-18 03:19:21 -07:00
David Aguilar 8341deb6ef cmdfactory: Add the Diff and ResetMode commands
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-18 03:19:00 -07:00
David Aguilar fb4669dc18 commands: Add a Diff command
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-18 03:18:24 -07:00
David Aguilar fbaf4fdc04 commands: Add a ResetMode command
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-18 03:18:10 -07:00
David Aguilar 55100b49c6 commands: Update diffstat function to not issue notifications
Notification is handled internally by the set_current_text call.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-18 03:17:37 -07:00
David Aguilar b7716f0c81 commands: Add docstrings to the base Command class
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-18 03:16:15 -07:00
David Aguilar 67c15a34d6 cola: Add a gitcola module and move the GitCola class there
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-18 03:15:03 -07:00
David Aguilar 0d60e6c12a errors: Add a GitInitError class
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-18 03:13:25 -07:00
David Aguilar 0c59c561e3 main: Instantiate the command factory on startup
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-18 01:46:20 -07:00
David Aguilar 0541f33459 cola: Add a command factory for generating editor commands
The factory singleton is in charge of {un,re}do and mapping cola signals
to commands.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-18 01:44:52 -07:00
David Aguilar 501db700d9 cola: Add a 'commands' module for state-modifying commands
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-18 01:40:58 -07:00
David Aguilar 28f4853d38 views.main: Listen for 'text' messages from the cola notifier
The main display now updates itself via the messaging interface.
This allows any object to update it by issuing text signals over
the notifier.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-18 01:38:48 -07:00
David Aguilar 848d07fbae cola.notification: Add a 'listen' method for listener registration
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-18 01:36:42 -07:00
David Aguilar 85870c5e4d models.main: Add a current_text field around diffstat.
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-18 01:33:51 -07:00
David Aguilar 96893a045c signals: Add a 'text' signal for updating the main display
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-18 01:29:36 -07:00
David Aguilar 3ce22bf641 views.status: Add selection and single_selection methods
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-17 22:40:59 -07:00
David Aguilar 18d6355a0a views.status: Add a selection callback for broadcasting Qt signals
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-17 22:40:59 -07:00
David Aguilar 432b5f9c21 views.status: Add context menus to the status tree
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-17 22:40:58 -07:00
David Aguilar e7c14e92fc models.main: Initialize mode to mode_none
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-17 22:40:58 -07:00
David Aguilar 41abccd7be qtutils: Add a SLOT method for broadcasting signals over the notifier
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-17 22:40:58 -07:00
David Aguilar 1e7b9f0e84 views.status: Add convenient selection methods
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-17 22:40:58 -07:00
David Aguilar bc72594684 cola: Add a 'notification' module to provide cola.notfier()
The notifier is used to broadcast Qt signals globally.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-17 22:40:42 -07:00
David Aguilar df2e73354f cola: Add a 'signals' module for storing common Qt signal definitions
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-17 22:40:42 -07:00
David Aguilar bb3f16600d doc: Add cola.views.status to the sphinx module index
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-16 22:52:24 -07:00
David Aguilar f90613af28 views.status: Add a docstring for the status widget
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-16 22:49:05 -07:00
David Aguilar b56fd7e990 models.main: Update branch_status to know about changed_upstream
We forgot to update this function when the tracked_branch feature was
added to the classic view.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-16 22:03:04 -07:00
David Aguilar f6348644c4 views.status: Automatically expand category folders
This opens each category list when items are added.
We only expand the category folder once so that users
can choose to keep a category closed.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-16 21:56:20 -07:00
David Aguilar 3a3015bef5 cola.views: Add a 'status' module to replace the existing status view
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-15 21:16:21 -07:00
David Aguilar ed9dde31e2 views.main: automatically expand top-level items
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-15 21:15:57 -07:00
David Aguilar c4169adfa9 models.main: Use git diff ..origin/master to check for upstream changes
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-15 03:57:23 -07:00
David Aguilar e91efd4f30 views.mainwindow: Update the logger when the font changes
This adds the logger to the font-update hook in the
main controller module.

Closes #20

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-15 02:32:07 -07:00
David Aguilar b3910538e1 ui: Remove the unused commit.ui file
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-15 02:32:05 -07:00
David Aguilar be95d6135f controllers.repobrowser: Use the new SelectCommits view
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-15 02:32:05 -07:00
David Aguilar ba0827a533 controllers.selectcommits: Use the new SelectCommits view
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-15 02:32:05 -07:00
David Aguilar ff7fec729a views: Add a SelectCommits view
This class will replace the codegen .ui class.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-15 02:32:05 -07:00
David Aguilar 4993a04394 models.main: Add mode states
This will let us know when we're in special states such
as diff-mode.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-15 02:32:05 -07:00
David Aguilar 63db0e9294 models.main: Add an 'updated' notification message
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-15 02:32:05 -07:00
David Aguilar 6189e11c8b controllers.main: Use the qtutils font functions
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-15 02:32:05 -07:00
David Aguilar 215d13fc96 controllers.options: Use the static model instance
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-15 02:31:55 -07:00
David Aguilar dde1d7f638 cola classic: Use the static model instance in the clasic view
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-15 02:27:52 -07:00
David Aguilar 65d6048a1a views.main: Use qtutils font management functions
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-15 02:27:41 -07:00
David Aguilar fb9dbc5672 models.main: Update git config handling for fonts
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-15 02:27:35 -07:00
David Aguilar 1a31a1b967 qtutils: Add font management functions
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-15 02:27:28 -07:00
David Aguilar 7e682fe21a utils: Add an is_darwin() function
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-15 02:27:19 -07:00
David Aguilar 3a0b6bd7fc qtutils: Remove unused functions
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-15 02:27:11 -07:00
David Aguilar 602fa077b6 cola classic: Add a function to return a static instance
model() provides a static instance of a ClassicModel.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-15 02:26:43 -07:00
David Aguilar 111c905269 models.main: Add a function to return all files in a repository
everything() returns tracked and untracked entries in one shot.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-15 02:26:22 -07:00
David Aguilar 10b0f1627c models.main: Add a function returning a static model instance
The model() method makes it easy for callers to access the main
model instance.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-15 02:26:12 -07:00
David Aguilar 76ded5384b qobserver: Properly handle QFontComboBoxes
This changes QFontCombobox to match the newer Qt API.
Instead of modifying the in-place currentfont we now create a
new QFont instance and vivify it from the model's value.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-15 02:26:00 -07:00
David Aguilar 4dc300de6b cola.qtutils: Use a separate dictionary create_treewidget_item
create_treewidget_item and cached_icon shared the same dictionary for caching,
so make create_treewidget_item use its own cache.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-14 00:15:18 -07:00
David Aguilar bc78c5e5b0 cola.utils: Add missing errno import
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-14 00:15:18 -07:00
David Aguilar f73b97bdd1 cola.ui: Remove unused logger.ui file
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-14 00:15:18 -07:00
David Aguilar cf60c19be6 cola: Inherit the system default font
We no longer support the cola.fontui configuration setting.
cola will always inherit the system default font which
simplfiies things for users and makes cola integrate
better style-wise with other applications.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-14 00:15:18 -07:00
David Aguilar c803b6a1b6 cola: Remove the 'model' module
This completes the refactoring to eliminate the cola.model module.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-11 03:04:18 -07:00
David Aguilar 639f6c37d2 cola.models: Add a 'base' module and BaseModel class
This is the first step in a refactoring to eliminate the
cola.model module.

Signed-off-by:  <davvid@gmail.com>
2009-09-11 03:02:41 -07:00
David Aguilar ffdf9c636e cola.qtutils: Move translation calls into helper functions
It was previously the caller's responsiblity to translate the
strings used by 'prompt', 'question', and 'information'.

This moves the translation calls into the functions and thus
removes the burden from the callers.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-11 02:18:24 -07:00
David Aguilar a595965aee cola.git: Remove mentions of 'input'
This renames the 'input' variable used in 'shell_quote' to avoid
using the same name as the Python builtin.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-01 02:06:44 -07:00
David Aguilar 13cc9f3e9a cola.utils: Remove mentions of 'input'
This commit renames 'sanitize_input' to 'sanitize' and removes
any changes the function to avoid the using variables named
'input'.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-01 02:05:04 -07:00
David Aguilar 8900b704fe cola.qtutils: Rename 'input' to avoid the Python builtin
qtutils.input is now qtutils.prompt.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-01 02:02:43 -07:00
David Aguilar fb655db4d9 search: Add a custom model for the search dialog
This eliminates the TODO for writing a custom search model.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-09-01 01:59:16 -07:00
David Aguilar 406abbd56f Merge branch 'maint' 2009-08-22 17:20:10 -07:00
David Aguilar 944ca1252f cola.models.gitrepo: Store a reference to the GitRepoInfoTask
PyQt will garbage collect QRunnable tasks unless we are careful
to store a reference to the task upon creation.

This fixes a segfault(!) on PyQt 4.5.
http://github.com/davvid/git-cola/issues/#issue/18

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-22 16:27:48 -07:00
David Aguilar 0b0b0db296 cola.model: Remove more magic
cola.model previously provided closures over attributes for calling
getattr(self, 'x') as self.get_x().  This is unused so remove it.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-21 01:46:15 -07:00
David Aguilar 7e6ebf79a5 Merge branch 'maint'
Conflicts:
	cola/models/main.py
2009-08-19 19:34:29 -07:00
David Aguilar 18cbc33b4c cola.models.main: Fix a bug when pushing branches
This was reported by Vacecchin on mailing list.
http://groups.google.com/group/git-cola/browse_thread/thread/4c8d21cb40aca90b

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-19 19:32:08 -07:00
David Aguilar 0becbc5943 Merge branch 'maint'
Conflicts:
	cola/main.py
2009-08-19 17:16:36 -07:00
David Aguilar 7b266ab3d5 cola.views.main: Defer expanding top-level items
Expanding top-level items at startup causes X errors.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-19 17:14:54 -07:00
David Aguilar 307e25860b cola.models.main: Call worktree_state earlier
This makes sure that the 'unstaged' attribute is properly
initialized.  Not doing so can make the unstaged list empty,
thus resulting in status tree events that do nothing.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-19 17:00:00 -07:00
David Aguilar e8ad3e0807 cola.models.main: Remove rename flag for tracked branches
We shouldn't check for renames if upstream changed because
we don't handle it and things are simpler if we treat renames
as a simple add and deletion.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-19 16:59:32 -07:00
David Aguilar f6310c1697 cola.views.main: Remove show_editor()
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-19 16:59:28 -07:00
David Aguilar 5ebdb1402f Merge branch 'maint'
Conflicts:
	cola/controllers/main.py
	cola/models/main.py
	cola/views/main.py
2009-08-19 16:59:08 -07:00
David Aguilar 2e879d830e cola.views.main: Handle None in set_display()
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-19 16:56:41 -07:00
David Aguilar 3142c93433 cola.qtutils: Handle None in set_clipboard()
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-19 16:36:47 -07:00
David Aguilar 47bff97d9b cola.models.main: Properly handle renames
Instead of trying to parse the rename information,
simply ignore renames and treat them like an add and
remove, which is exactly what the GUI wants to display
anyways.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-19 16:36:17 -07:00
David Aguilar 6963e48631 cola.models.main: Do not use --patch-with-raw
We don't need the raw patch output so don't ask for it.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-19 16:35:13 -07:00
David Aguilar 2288385ef2 cola.controllers.main: Explicitly initialize the mode attribute
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-19 16:25:26 -07:00
David Aguilar 3be5dffb7d cola.utils: Handle interrupted system calls from platform.system()
Stupid OSX.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-19 16:23:56 -07:00
David Aguilar 10a2f494e3 cola.utils: Handle interrupted system calls from platform.system()
Stupid OSX.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-18 18:55:03 -07:00
Uri Okrent 9252246993 Detect upstream path changes on tracking branches
In the classic view, when the current branch is tracking a remote
branch, detect when there are changes to paths on the tracked branch.
The 'Changed Upstream' status is lower priority than local changes
(e.g., if a file is changed upstream, but is also locally modified,
the classic view will show it as locally modified, but not changed
upstream, since that is probably just confusing and there is limited
space in the status area).

A corresponding new action to 'checkout updated file' should be added
to the list of actions for paths, and enabled when the path is
'changed upstream'.

Signed-off-by: Uri Okrent <uokrent@gmail.com>
2009-08-18 18:54:39 -07:00
Uri Okrent fa5e223963 cola.views.main: guard against silliness with str
Signed-off-by: Uri Okrent <uokrent@gmail.com>
2009-08-18 18:40:49 -07:00
David Aguilar 7d46572905 cola.views.standard: Position windows beneath their parent's titlebar
This modifies the show() method to position new dialogs lower
on the screen such that their parent's titlebar are no longer
obscured.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-16 12:56:18 -07:00
David Aguilar 08ac8e2b4a ui: Remove the merge interface file since it is now unused
This file is unused now that cola.views.merge is integrated.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-15 14:09:48 -07:00
David Aguilar 3d95098c7b cola.controllers.merge: Integrate the new MergeView
This replaces the .ui-based MergeView with the new one provided
by cola.views.merge.  This also moves some code from the
controller into the view where it rightfully belongs.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-15 14:06:54 -07:00
David Aguilar 1517d8c95a cola.views: Add a merge view
cola.views.merge will replace the merge dialog provided by
the .ui file.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-15 14:06:04 -07:00
David Aguilar 3e40338a29 cola.controllers.merge: Use existing model attributes
Reuse exising model attributes in the merge controller
to avoid having to add them at runtime.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-15 13:00:55 -07:00
David Aguilar cc3e7574b8 cola.models.main: Move project and git_version attributes into MainModel
This removes the monkey-patching done in controllers.main by moving
attributes into the model.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-15 12:52:02 -07:00
David Aguilar 0a75b75e46 tests: Eliminate Makefile redundancy and add a test_flags variable
The top-level Makefile now delegates to the test/Makefile when
running tests.  This removes the repeated nosetests commands
in both Makefile and test/Makefile.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-15 12:50:12 -07:00
David Aguilar 52c5a4635f cola.models: Wrap compare-specific attributes into a custom model
This adds a CompareModel to encapsulate the attributes specific
to the compare-commits actions.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-15 04:44:09 -07:00
David Aguilar d76984b277 doc: Document qtutils, views.log view.mainwindow, and views.syntax
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-15 02:56:19 -07:00
David Aguilar 45ccd675c7 Merge branch 'maint' 2009-08-15 02:27:27 -07:00
David Aguilar be3e2f5ddd doc: Make conf.py a phony Makefile target
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-15 02:21:12 -07:00
David Aguilar 76ff0d8ad1 cola.controllers.classic: Expose public methods
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-15 02:21:03 -07:00
David Aguilar 195bf9c82b doc: Document the classic view modules
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-15 02:20:55 -07:00
David Aguilar cc2f574b1c doc: Update the Makefile to not show rsync progress
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-15 02:20:47 -07:00
David Aguilar 7928c09e5c cola.views.mainwindow: Dockify the 'cola classic' widget
This adds a dockwidget for the 'cola classic' tool, thus making
it just like any other cola widget.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-15 00:36:58 -07:00
David Aguilar 110a122409 cola.controllers.classic: Add a 'widget' convenience method
The 'widget' call allows us to easily create a 'cola classic' widget.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-15 00:35:37 -07:00
David Aguilar a6e0d2c9a6 cola.views.mainwindow: Add menu entries for all of the cola tools
This adds each dock widgets' toggle-view action to the tools menu.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-15 00:33:55 -07:00
David Aguilar 4b11222a8f Merge branch 'maint-v1.3.9' 2009-08-14 22:41:40 -07:00
David Aguilar b7c534a9a4 userguide: Add the classic view shortcut keys
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-14 22:41:32 -07:00
David Aguilar c94687fa36 cola: Add a --classic command-line flag
The classic view can now be run in standalone mode by running:
	git cola --classic

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-14 13:20:00 -07:00
David Aguilar c8252789f3 cola.views: Remove the unused drawer widgets
The new UI doesn't use the drawer widgets so remove them.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-14 13:14:35 -07:00
David Aguilar bc1e30afc4 ui: Remove the old main window interface file
We no longer use pyuic for the main window so remove its .ui.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-14 03:00:02 -07:00
David Aguilar bb1c92d13e cola.views.main: Integrate the new MainWindow class
The cola UI is now based upon dock widgets and is thus much
more flexible.  Individual interface elements can now be
rearranged and configured at runtime.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-14 02:59:54 -07:00
David Aguilar d5164942bf cola.controllers: Remove the 'log' module
This removes the last remains of the old logging system.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-14 02:46:25 -07:00
David Aguilar bde58dd676 cola.views.log: Integrate the new logger
This removes all references to the old logger and adds the
bootstrap method to qtutils for creating the log widget.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-14 02:45:46 -07:00
David Aguilar 1ef9beb68b cola.views: Add a new LogView class
This simple widget is going to replace the pyuic-based widget.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-14 02:43:25 -07:00
David Aguilar 7405485930 cola.views.mainwindow: Introduce MainWindow
MainWindow is slated to replace the main cola UI.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-14 02:41:12 -07:00
David Aguilar 8a96018f76 test_cola_model: Update the tests to remove the magic getters
We no longer need the magic getter methods so don't test them.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-14 00:39:34 -07:00
David Aguilar 783601f183 cola.views.standard: Refactor the class closure
This exposes a new create_standard_widget function which is used
as a building block for create_standard_view.  This allows us to
decorate simple Qt widgets with style and preference methods.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-14 00:37:32 -07:00
David Aguilar fa764eaa68 cola.views.syntax: Death to 'get'
This makes the syntax module consistent with the rest of cola.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-14 00:35:44 -07:00
David Aguilar f7edf914e4 cola.qobserver: Allow nameless widgets
QObserver previously required that all widgets provide a unique
object name.  QObserver can now work with nameless or arbitrarily
named widgets.  This decouples the controller from the view and
also makes it simpler to write new views.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-14 00:35:16 -07:00
David Aguilar 012ed5fe4f cola.model: Death to 'get'
This finally removes the last remaining bits of the 'get_*' magic.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-14 00:31:53 -07:00
David Aguilar 6ad07e2794 cola.utils: Make run_cmd easier to use
run_cmd() now takes a list instead of using *args for no reason.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-14 00:26:22 -07:00
David Aguilar 1fc0f44a70 cola.version: Death to 'get'
This makes cola.version consistent with the rest of cola.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-14 00:25:25 -07:00
David Aguilar 3bead4b6a7 cola.controllers.util: Death to 'get'
This removes usage of the magical model 'get_*' methods.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-14 00:15:49 -07:00
David Aguilar 28bc6df8f6 cola.controllers.stash: Death to 'get'
This removes usage of the magical model 'get_*' methods.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-14 00:09:44 -07:00
David Aguilar 0faf4e011f cola.controllers.selectcommits: Death to 'get'
This removes usage of the magical model 'get_*' methods.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-14 00:09:05 -07:00
David Aguilar 04f1a7a39a cola.controllers.search: Death to 'get'
This removes usage of the magical model 'get_*' methods.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-14 00:07:46 -07:00
David Aguilar 2d15bc253b cola.controllers.repobrowser: Death to 'get'
This removes usage of the magical model 'get_*' methods.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-14 00:07:02 -07:00
David Aguilar aa9f075c13 cola.models.main: Death to 'get' for config methods
This removes the use of the magical 'get_*' methods.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-14 00:03:41 -07:00
David Aguilar 4092a02c5e cola.settings: Death to 'get'
Writing getters is very unpythonic.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-13 20:32:16 -07:00
David Aguilar f1beaee649 cola.controllers.{main, merge, remote}: Death to 'get'
This removes usage of the magical model 'get_*' methods.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-13 20:30:37 -07:00
David Aguilar 2308c9215b cola.controllers.createbranch: Death to 'get'
This removes usage of the magical model 'get_*' methods.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-13 20:04:11 -07:00
David Aguilar 919bbb16d9 cola.controllers.compare: Death to 'get'
This removes usage of the magical model get_* methods.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-13 20:04:11 -07:00
David Aguilar ff323db57f qtutils: Death to 'get'
Writing getters is very unpythonic.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-13 20:04:11 -07:00
David Aguilar d44eecfefd cola.models.main: Death to 'get'
This replaces get_history_browser() with history_browser()
and get_editor() with editor().

Writing getters is very unpythonic.
2009-08-13 20:03:29 -07:00
David Aguilar 4eecad9c99 cola.app: Death to 'get'
Writing getters is very unpythonic.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-08-13 19:28:38 -07:00
David Aguilar 8221f8b4db cola.main: Defer showing the GUI until it has been raised
This makes the GUI appear to startup faster since it is already raised
when it initially appears.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-07-18 23:47:21 -07:00
David Aguilar ba092d3da6 cola.controllers.main: Add 'Cola Classic'
The tools menu now has a functioning 'Cola Classic' browser.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-07-18 23:43:19 -07:00
David Aguilar a7da3ff03e cola.controllers.classic: Add a function for launching the classic view
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-07-18 23:39:00 -07:00
David Aguilar ce542b525c cola.gui: Add a 'Cola Classic' tools menu entry, resize the CommitView
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-07-18 23:18:48 -07:00
David Aguilar 559253e311 Merge branch 'classic' 2009-07-18 23:14:44 -07:00
David Aguilar ae745aa1b4 cola.app: Wrap setStyleSheet() to fix custom styles
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-07-18 23:14:26 -07:00
David Aguilar 69e0c7a348 classic: Add 'Launch Editor' to the tree view widget
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-07-18 23:04:02 -07:00
David Aguilar cb1e8aa6fe simplejson/ordered_dict: Support Python2.4
The ordered_dict popitems() method was using a syntax that was not
understood by Python2.4, so use a backwards-compatible syntax.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-07-13 17:45:35 -07:00
David Aguilar d68e297009 views/repo: Use a helper function for setting the column sizes
This refactors things to use a function instead of an inline
lambda for setting the column sizes.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-30 03:43:24 -07:00
David Aguilar 30ac94119f classic: Rework header columns
The header columns are now:
	Name, Status, Age, Message, Who

'Name' is the file path.
'Status' is the paths' status.
'Age' is the time since the latest commit.
'Message' is the subject of the latest commit.
'Who' is the author of the latest commit touching the path.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-30 03:37:47 -07:00
David Aguilar e2f490c223 classic: move the difftool_predecessor logic into the controller
Launching the 'select commits' dialog and processing the results is
a task for the controller, not the view.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-25 07:26:25 -07:00
David Aguilar 1c6522cee7 views/repo: store the app model in an instance variable for simplicity
Instead of calling self.model().app_model we now store a reference
in setModel() and use it throughout the RepoTreeView.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-24 23:12:55 -10:00
David Aguilar 94acaa7c8a views/repo: change the 'View History...' shortcut to avoid clashes
Cmd+H is 'Hide' on Mac OS X.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-24 23:10:12 -10:00
David Aguilar 4eb65529f2 views/repo: add Vim keybindings to the RepoTreeView
Git users are power users, so support vim keybindings.
We might want to be able to configure this in the future.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-24 22:58:04 -10:00
David Aguilar f9c2c11015 views/repo: improve performance by setting uniformRowHeights
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-24 22:52:10 -10:00
David Aguilar 060d535cc3 views/repo: fix selected_tracked_paths()
selected_tracked_paths() was not considering unmodified entries as
tracked.  It now looks uses information from tracked, staged,
and modified entries when deciding if a path is 'tracked'.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-24 22:51:09 -10:00
David Aguilar a8fccf5a8a views/repo: update all shortcut keys to use a modifier
We now use Ctrl+<Key> for all shortcuts.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-24 21:39:20 -10:00
David Aguilar f2e83544a7 views/repo: update actions when paths are updated
This updates the notfication hook so that it update the available
actions when the model changes.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-24 21:22:42 -10:00
David Aguilar 8f822c15fc views/repo: add a 'Diff Against Predecessor...' action
This teaches the RepoTreeView to diff paths against previous versions.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-24 19:44:46 -10:00
David Aguilar 999ad49294 models/main: add the ability to pass extra arguments to log_helper
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-24 19:43:20 -10:00
David Aguilar 03bc79848a controllers/selectcommits: set the main UI font in select_commits
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-24 19:42:53 -10:00
David Aguilar 57b743e0b0 ui/commit: disable line-wrapping by default
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-24 19:42:28 -10:00
David Aguilar 8eb6834895 views: add an option to disable multi-select in the CommitsView
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-24 19:42:08 -10:00
David Aguilar b30546aafc views/repo: automatically select the first entry when expanding
We now select the first entry by default when directories are expanded.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-24 18:44:06 -10:00
David Aguilar b6815b929c classic: add a 'Revert Uncommitted Changes...' action
This adds the MVC hooks to views/repo and controllers/classic to
enable reverting paths back to their committed state.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-24 18:17:45 -10:00
David Aguilar 7d82503e6d models/classic: add a 'revert_paths' method
'revert_paths' removes local changes by reverting file paths back to
their content in the latest commit.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-24 18:14:40 -10:00
David Aguilar d9005f1653 classic: allow difftool to be run on multiple paths
The 'View Diff...' action now accepts multiple paths.
We can now select a directory or multi-select individual items
in the treeview when launching difftool.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-24 18:11:55 -10:00
David Aguilar d03c1bd09e models/observable: guard against bogus message names
This adds a register_messages() method to Observable for handling
message registration.  Unknown messages raise ValueError when
observers attempt to connect to them.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-24 18:08:30 -10:00
David Aguilar 099067d3df views/repo: only allow difftool for paths that are staged or modified
We should not enable difftool for untracked paths.  This changes the
update_actions() logic to disable difftool when the current path is
an untracked file.

A new helper 'selected_modified_paths()' method was added so that
we can detect whether a path is modified.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-24 14:13:46 -10:00
David Aguilar c514ff1f46 views/repo: add a current_path() method and use it in difftool()
current_path() returns the path to the current item in the treeview.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-24 14:03:49 -10:00
David Aguilar b39d9f3fc3 classic: add a 'View Diff...' action to the RepoTreeView
The classic RepoTreeView now supports git-difftool by using the 'D'
shortcut key.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-24 11:54:32 -10:00
David Aguilar 4b59a5a19d views/repo: remove misleading '..' characters from non-dialog actions
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-24 11:53:05 -10:00
David Aguilar 9853b7ac0e views/repo: update available actions when the selection changes
Actions are dependent on the selection so teach selectionChanged()
to update the available actions.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-24 11:34:52 -10:00
David Aguilar 6342cfd10c views/repo: move the cursor to the parent directory on Key_Left
When Key_Left is pressed we should automatically move the cursor
to the parent directory when the cursor is either on a file
entry or a collapsed directory.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-24 11:23:07 -10:00
David Aguilar 97587163a6 classic: add an 'unstage selected' action
This adds the appropriate MVC stubs for supporting 'unstage selected'
in the classic repo tree view.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-23 23:43:36 -10:00
David Aguilar 6e7d1c77c5 views/repo: add context menu entries for 'stage selected'
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-23 23:41:44 -10:00
David Aguilar b2a2428b83 models/classic: add an 'unstage_paths' method
'unstage_paths' removes paths from the staging area and notifies
observers about newly unstaged paths.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-23 23:13:02 -10:00
David Aguilar 85ae55a2bd models/classic: properly handle untracked files in stage_paths()
We weren't properly detecting newly-staged files that came in as
a result of staging a subdirectory.  Detect this condition and
add a testcase.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-23 23:09:23 -10:00
David Aguilar 54724a64e1 models/classic: s/paths_staged_message/message_paths_staged/
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-23 22:23:10 -10:00
David Aguilar 4e8a773ce8 models/classic: make stage_paths() easier to use
stage_paths() is now more resilient to changes done behind
its back.  This updates the test to reflect the simpler API.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-23 21:41:43 -10:00
David Aguilar 4ffb544197 models/gitrepo: handle untracked items in GitRepoModel
GitRepoModel now knows about untracked entries.
'Last Modified' information is gathered by querying the filesystem.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-23 21:24:02 -10:00
David Aguilar 56d10d420c models/classic: add an 'everything' method to return all files
'everything' returns the combined output of 'git ls-files' and
'git ls-files --others --exclude-standard' in a sorted list.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-23 21:23:30 -10:00
David Aguilar b6a59881fd classic: use observation for handling updates to 'staged' files.
The controller no longer modifies the view directly.
Instead, the view registers for the 'paths_staged' message
from the model and reacts accordingly.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-23 21:23:30 -10:00
David Aguilar 6d69171194 views/repo: shuffle some functions around for better readability
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-23 21:23:30 -10:00
David Aguilar 2f1db8f5b2 models: add a ClassicModel for use by the RepoTreeView
The ClassicModel has specialized functions for use by the RepoTreeView.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-23 21:23:14 -10:00
David Aguilar 17b7abf806 models/main: handle unicode decoding within the all_files() method
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-23 21:00:54 -10:00
David Aguilar dbb2476b13 models/gitrepo: add a GitRepoModel.entry() method
This makes things easier to read by adding an entry() method and
using it in add_file() / add_directory().

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-23 20:58:35 -10:00
David Aguilar de5155f265 models/gitrepo: fix a bug when adding directory entries
We now use insertRow() / appendRow() when adding rows to
GitRepoItems.  This fixes a bug where directories were overwriting
existing file entries.

This introduces a _create_row() method to handle creating a
GitRepoItem for each 'Status', 'Modified', etc. cell.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-23 20:48:00 -10:00
David Aguilar 1c65b14faa models/gitrepo: add translation calls for headers and status labels
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-23 20:33:13 -10:00
David Aguilar 34cc62a44b models/gitrepo: use utils.add_parents()
This updates GitRepoInfoTask to use the utils.add_parents() function.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-23 20:26:58 -10:00
David Aguilar 58d2760f63 cola/utils: add a add_parents() function
add_parents() iterates over a set and adds all parent directories
for each file entry to the set.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-23 20:16:42 -10:00
David Aguilar 4556017936 models: add message observers for handling more granula notifications
'message observers' allow observers to attach methods onto model
notification events.  Instead of a following a specific interface,
observers are free to provide any Python method as an 'observer'
which is invoked whenever notification occurs.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-23 16:30:17 -10:00
David Aguilar 38fe56de64 models/observable: refactor addition/removal of unserializable attributes
ObservableModel's unserializable data is now encapsulated into a single
dictionary that is updated whenever the new unserializable data is
introduced.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-23 15:57:06 -10:00
David Aguilar e6664ff143 models: refactor notification state to be a simple Python attribute
Models now have a simple 'notification_enabled' attribute that is
accessible to the outside world.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-23 13:17:44 -10:00
David Aguilar 36024f5fe0 classic: add support for staging files from the 'classic' view
The 'S' key can now stage selected files.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-23 12:55:05 -10:00
David Aguilar f9fbb5b2c9 views/repo: refactor action creation into a method
We now use a _create_action() method for creating QActions.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-23 12:53:32 -10:00
David Aguilar 4309d9a358 models/gitrepo: do not keep around static status data
Using static status data will make it harder to perform tasks such as
'stage selected', etc. from the repo view, so make all status data
ephemeral.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-22 00:06:23 -10:00
David Aguilar bda9339e7b controllers/classic: wait for all threads to finish before exiting
The standalone repo view should wait for all threads to finish before
exiting the application.  Failure to do so causes Python to explode.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-22 00:03:49 -10:00
David Aguilar fb174a8cf6 models/gitrepo: make GitRepoItem.connect() take no parameters
'signal' is already an instance variable so there's no need to pass
it down to connect().

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-22 00:02:13 -10:00
David Aguilar 9641c0bc56 cola/git: escape the carat '^' character on Windows
The carat character is itself a reserved shell character.
Thus, to type a carat character as part of a command argument,
type two carats instead.

http://technet.microsoft.com/en-us/library/cc723564.aspx

This was breaking both diff-mode and the amend-commit
functionality.

Closes #13

Signed-off-by: David Aguilar <davvid@gmail.com>
Reported-by: Jorge <boxerab@gmail.com>
2009-06-20 22:24:09 -10:00
David Aguilar 25e052d761 cola/utils: check for filename and filename + '.exe' in fork() on win32
When using fork() on win32 we should check for the existence of a
filename with and without the '.exe' extension.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-20 18:12:22 -10:00
David Aguilar 3c7f662b27 models/gitrepo: mark directories with status information
We now traverse parent directories and mark them with status information
corresponding to their child entries.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-20 18:11:56 -10:00
David Aguilar e6b03d3c17 models/gitrepo: use utils.dirname and utils.basename
This updates cola.models.gitrepo to use the utils.{dir,base}name
functions consistently.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-20 18:06:04 -10:00
David Aguilar 386e504087 utils: make utils.basename more robust and add utils.dirname
This adds a utils.dirname() implementation for cross-platform use.
We don't use os.path.{dir,base}name since git always uses
'/' regardless of platform.

This also adds a testcases for dirname and basename.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-20 18:00:29 -10:00
David Aguilar e7fff06e73 controllers/classic: optimize the update() criteria
We used to spawn a new thread for every column in the tree view.
We only need to spawn one, actually, since all of the columns
re-use the same entry and thus calling update() three times is
wasteful.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-20 16:10:01 -10:00
David Aguilar 237ae18ebe classic: launch update threads as directories are expanded only
We were previously creating new threads for every single path in
the repo at once.  We now spawn threads as new directories are
exposed.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-20 12:11:00 -10:00
David Aguilar 4af1496977 models/gitrepo: add GitRepoItems for directory entries
Directories now have the standard 'Name', 'Status', 'Message',
and 'Last Modified' headers.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-20 12:08:42 -10:00
David Aguilar 2e76874d6d models/gitrepo: properly handle unicode when querying path info
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-20 12:07:06 -10:00
David Aguilar 43fef127dd views/repo: add an item_from_index() method
item_from_index() returns the GitRepoItem corresponding to a
QModelIndex.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-20 12:04:30 -10:00
David Aguilar 5ad99b254b models/gitrepo: drop the --no-merges flag
It is completely conceivable that a path was only ever been touched by a
merge so we shouldn't use --no-merges when looking up data about that
path.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-20 11:17:19 -10:00
David Aguilar 6031eae772 ui/main: fix a typo in the File -> View Log menu item
The File > View Log menu item was misspelled as File > VIew Log,
so fix it.

Closes #14

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-20 01:34:57 -10:00
David Aguilar 55b67cfc47 classic: add more columns and update them asynchronously
We now use a QThreadPool to update information about items
in the repo view.  Status information is gathered in the
background so that the GUI can load more quickly.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-20 01:30:05 -10:00
David Aguilar 222b6b4bca views/repo: set column size on collapse as well as expand; use animation
Collapsing as well as expanding tree items now resizes the 0th column.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-20 01:27:54 -10:00
David Aguilar 83fdae261c views/main: clear the diff display when exiting diff mode
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-18 23:03:52 -10:00
David Aguilar 948d4e82c5 models/gitrepo: use standard file and directory icons from qtutils
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-18 14:16:08 -10:00
David Aguilar 510d53459a qtutils: add file_icon() and dir_icon() functions
These functions return standard icons for files and directories.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-18 14:15:35 -10:00
David Aguilar 981ca7bda4 models: use QStandardItemModel as the base class for the new GitRepoModel
Using QStandardItems allows us to simplify things since they handle
much more vs. the QAbstractItems we were using previously.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-18 13:49:48 -10:00
David Aguilar 3353ba1aad INSTALL: mention pyinotify >= 0.7.1
Gentoo reported that pyinotify 0.7.0 is broken so mention
that we require pyinotify >= 0.7.1.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-18 02:44:47 -07:00
David Aguilar 356d1f47e0 views/main: introduce {enter,exit}_diff_mode() and use it
This introduces a convenient enter/exit_diff_mode() method on the
main view and teaches the main controller to use it when appropriate.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-18 01:49:50 -07:00
David Aguilar 7ebc3b30ce models/main: properly handle diff expressions with whitespace
We did not properly handle 'HEAD^ HEAD' because of the embedded space.
We now properly handle this by splitting arguments before passing
them along to git.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-18 01:25:59 -07:00
David Aguilar 2fdcc16628 models: introduce get_diff_by_expr() and use it in the main controller
We no longer rely on heuristics when deciding how to construct
arguments intended for git diff.  The controller knows what mode
it's in and thus never rely on heuristics.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-18 01:24:37 -07:00
David Aguilar 8a9726ef2d models/main: use a more robust method to detect when not to use --cached
We were incorrectly passing --cached in some instances when using
`diff-by-expression` or `diff-mode`.  This fixes things so
that we are more robust to free-form input.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-18 00:50:22 -07:00
David Aguilar cae2b6e47e branch review: use $(git merge-base HEAD branch)..branch in review mode
We used to use ...branchname when showing the diff for review mode.
We now use `$(git merge-base HEAD branchname)..branchname` since it
is more robust.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-18 00:35:51 -07:00
David Aguilar 66cd8a8ed7 controllers/main: always use 'HEAD' when undoing changes
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-16 21:23:10 -07:00
David Aguilar ccf1ca126c classic: handle the 'history(QStringList)' signal from the RepoTreeView
We now launch the configured history browser on the selected paths.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-16 04:16:43 -07:00
David Aguilar db946108aa views: add a RepoTreeView for use in the classic view
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-16 04:16:11 -07:00
David Aguilar a3b6fb5cc0 gitdirmodel: store the original git path in a read-only 'path' property
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-16 04:15:41 -07:00
David Aguilar cbeae4ef7b classic: move model initialization code into the model itself
The (currently empty) controller knew too much about the model's
internals, so refactor that knowledge it into the model itself.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-15 02:59:09 -07:00
David Aguilar 99e6dfbc00 controllers: add a 'classic' module to implement a tkcvs-like view
Users have often asked to see the status of their entire project.
The 'classic' controller provides this behavior.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-15 02:13:06 -07:00
David Aguilar 5ca3c53ccd models: add a gitdirmodel module
The gitdirmodel module provides the GitDirModel class for
use by the Qt model/view architecture.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-15 02:13:06 -07:00
David Aguilar 42e7ec3e34 models.main: added a all_files() helper method
all_files() is a helper method around 'git ls-files -z' that returns
a list of all files in a repository.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-15 02:13:06 -07:00
David Aguilar 8616d2feba doc: tag sphinx documentation with the proper release + version
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-14 03:48:44 -07:00
David Aguilar 9751d6c8ee setup.py: launch pyuic4 with 'sh -c' to work around old pyuic4 bugs
Older versions of pyuic4 were implemented as a shell script instead
of as python script.  Interestingly, an older version existed
that didn't have a '#!/bin/sh' shebang line.

Use 'sh -c' to accomodate both current and older, buggy pyuic4s.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-14 02:53:10 -07:00
David Aguilar c3830cf1bf Show the diff when moving the selection using the arrow keys
We weren't reacting to the selectionChanged() callback and thus
were not displaying diffs when changing the selection using the
arrow keys.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-13 19:35:21 -07:00
David Aguilar 358d4c5801 userguide: describe the main elements of the cola interface
This adds descriptions for the repository status view, the diff view,
and the diff hunk regions.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-09 02:36:17 -07:00
David Aguilar 841f81c09f Move the sphinx docs under share/doc/git-cola
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-01 01:04:04 -07:00
David Aguilar cd36f8b074 Change doc version to release
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-06-01 00:58:35 -07:00
David Aguilar b193772809 controllers/main: adjust docstrings for readability
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-05-31 19:37:19 -07:00
David Aguilar fcf34a6223 darwin doc: mention the macports dev environment
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-05-31 19:36:35 -07:00
David Aguilar 52ef28a6b7 Merge branch 'documentation' 2009-05-27 10:37:44 -07:00
David Aguilar c7618a4cef doc: add documenation for the rest of the cola API
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-05-27 10:37:21 -07:00
David Aguilar 0a2baa24ea doc: add module documentation for base classes, models, controllers, and views
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-05-27 10:37:11 -07:00
David Aguilar d9b1d9b744 doc: add a skeleton API reference guide using sphinx
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-05-27 10:37:04 -07:00
David Aguilar ba552c30af doc: add the source root to sys.path
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-05-27 10:36:54 -07:00
David Aguilar 8d3293778c doc: add .gitignore for the _build, _static, _templates
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-05-27 10:36:47 -07:00
David Aguilar a18a8c3713 doc: add sphinx documentation with sphinx-quickstart
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-05-27 10:36:38 -07:00
David Aguilar 34000c7171 utils: fix the docstring for sphinx
"*" in rest means emphasis, so don't use it.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-05-27 10:32:05 -07:00
Uri Okrent 55d318a04b Add 's', 'h' shortcut keys for stage/unstage selection/hunk
Signed-off-by: Uri Okrent <uokrent@gmail.com>
2009-05-24 21:29:55 -07:00
David Aguilar b341236095 diff: add the ability to diff using an arbitrary diff expression
Diff -> Expression... allows you to enter a diff expression
such as master~3...master and see the results in the
Repository Status tree.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-05-15 02:21:35 -07:00
David Aguilar 861515ec96 Add a diff menu to contain diff-specific actions
Diff against SHA-1, diff branches, and diff commits should
all be in the same menu.  The "diff expression" action
can then go in this menu.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-05-15 02:20:25 -07:00
David Aguilar 525f2def04 Merge branch 'darwin'
* darwin:
  darwin: change setup-dev-env script to list the minimal dependencies
2009-05-11 00:50:05 -07:00
David Aguilar 2a4a2bb195 pep8: pepify and add more docstrings to the controllers package
Add missing docstrings to the entire controllers package.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-05-11 00:49:44 -07:00
David Aguilar bec3eb4ade Makefile: remove references to git-difftool
We no longer install git-difftool so we shouldn't try to
uninstall it.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-05-10 19:08:21 -07:00
David Aguilar 1eece4e275 controllers/main: remove the 'staged' parameter from delete_files
The 'staged' parameter was never used, so remove it.
This also means we can use the callback directly without
having to wrap it with a lambda.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-05-10 17:46:07 -07:00
David Aguilar cb1237ce10 darwin: change setup-dev-env script to list the minimal dependencies
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-05-10 15:47:31 -07:00
David Aguilar c577f70e69 controllers/createbranch: add docstrings and source comments
This adds docstrings and inline comments to the createbranch module.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-05-10 15:29:42 -07:00
David Aguilar 8b6a3543bf controllers/compare: use lambda's instead of closures
This eliminates the two gen_* methods by using lambdas
instead of closures.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-05-10 15:01:56 -07:00
David Aguilar 0b4df13e90 controllers/compare: add docstrings and source comments
This adds docstrings and inline comments to the compare module.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-05-10 14:59:39 -07:00
David Aguilar a31ffcae2f views: raise_() all dialogs
Some of the combo boxes were appearing below the main gui,
so raise_() them.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-05-10 02:10:08 -07:00
David Aguilar 220c58529a py2app-setup: fix the usage of the powerpc emulation option
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-05-08 01:19:14 -07:00
David Aguilar c8d3a23a4b darwin/__boot__: don't chdir to Resources
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-05-08 01:14:50 -07:00
David Aguilar 9fbe61f952 py2app-setup: use prefer_ppc when building on ppc
This should get our intel tiger mac users up and running.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-05-08 00:10:28 -07:00
David Aguilar f2663f82fd build: simplify Makefile further by letting python name the app tarball
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-05-08 00:03:17 -07:00
David Aguilar 1ccfdb2858 Makefile: make 'test' depend on 'all'
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-05-07 23:41:20 -07:00
David Aguilar a059ca47b6 macports-setup: simplify the instructions and make it sudo-compatible
The script now lists the minimum steps needed to bootstrap a cola dev
environment only.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-05-07 23:41:20 -07:00
David Aguilar 0c67af6f36 darwin: include system info in the generated app.tar.bz2 files
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-05-07 03:41:20 -07:00
Uri Okrent 78624f86fa inotify: fix a bug when exiting cola
git-cola would hang on Ubuntu systems when pyinotify was installed.
This fixes it by adding a setter for the GitNotifier.

Signed-off-by: Uri Okrent <uokrent@gmail.com>
2009-05-06 18:58:28 -07:00
David Aguilar 45b3a307fa Makefile: abbreviate the version SHA-1 to 4 characters
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-05-06 02:07:30 -07:00
David Aguilar 49c78bc3df Makefile: add a 'git-cola.app' target
'make git-cola.app' generates both the .app and the .app.tar.bz2
tarball in one step.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-05-06 01:54:01 -07:00
David Aguilar ac8426ca90 docs: use 'git-cola' consistently when referring to the script name
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-05-06 00:10:11 -07:00
David Aguilar c53acfdc01 INSTALL: update the git-on-windows url to point to msysgit
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-05-05 23:57:37 -07:00
David Aguilar bb1a58fefe INSTALL: mention using the stable releases
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-05-05 23:42:04 -07:00
David Aguilar f19e9e6590 documentation: remove git-difftool.txt, update git-cola.txt
git-cola.txt now mentions the environment variables
for difftool.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-05-05 22:19:51 -07:00
David Aguilar 0e8e9ccbc4 INSTALL: update the urls to point to PyQt, add more clarifications
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-05-05 21:51:10 -07:00
David Aguilar 6af868a3fc darwin: add an iconfile for git-cola.app
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-05-05 21:39:02 -07:00
David Aguilar f6605af340 Use SVGs instead of pngs for the main logo
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-05-05 21:39:02 -07:00
David Aguilar 55043cece1 darwin: add scripts for generating git-cola.app for OSX
'make macapp' now generates the git-cola.app OSX
application bundle.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-05-05 21:39:02 -07:00
David Aguilar c615e2d9b4 models: do not flag deleted files as modified
This fixes a bug where a deleted file would still appear
in the modified files list after staging.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-05-05 21:37:36 -07:00
David Aguilar 54068edd1f setup.py: add the private cola.models module
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-05-05 02:53:01 -07:00
David Aguilar 2f2428557e win32 installer: keep a copy of git-cola sans .pyw installed
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-05-05 02:25:30 -07:00
David Aguilar 272b961704 observable: use the current model's notify value when cloning
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-05-05 01:39:24 -07:00
David Aguilar 6f5a511665 models: fixed a bug in clone()
It was the best of times, it was the worst of times.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-05-05 01:33:04 -07:00
David Aguilar b809514eec drawerwidgets: add and use an add_bottom_drawer(widget) method
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-05-01 21:33:51 -07:00
David Aguilar 47210a699b views: move the about dialog into its own module
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-05-01 21:33:46 -07:00
David Aguilar f417208ba0 controllers/main: add more comments to the MainController class
All methods now have docstrings and better in-line comments.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-04-30 04:12:43 -07:00
David Aguilar 204e95f74e models: optimize update_status() by only checking for partial staging on staged items
We used to check every modified item to see if it was partially staged.
It's fast to only check the staged items.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-04-30 03:17:52 -07:00
David Aguilar 75fdacf0fe controllers/main: allow double-click / icon-click on unmerged items
We were previously guarding users against accidentally clicking on an
unmerged entry to stage it.  This was confusing

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-04-30 03:16:43 -07:00
David Aguilar 92140ea386 controllers/util: remove an unused import
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-04-30 02:29:06 -07:00
David Aguilar 66cf4644f1 bookmarks controllers: add comments to save_bookmark()
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-04-30 02:20:44 -07:00
David Aguilar 64c8aee39a refactor the cola package namespace
The models, views and controllers packages were doing too much in module __init__.
This simplifies things by doing nothing in __init__ and using the fully-qualified
package in the callers.

This adds a cola.models package that didn't exist before.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-04-30 02:06:14 -07:00
David Aguilar 98f2bb2dc7 Makefile: cleanup the install target, use rmdir -p
It'd be good to move more of this into setup.py.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-04-25 20:15:18 -07:00
David Aguilar f64efd389a tests: add test cases for the cola.settings module
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-04-25 19:48:03 -07:00
David Aguilar cbcb8f20c6 Makefile: do not rm -rf $(prefix)/lib
'make install' shouldn't delete the lib/ directory.
That was a bad mistake.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-04-25 15:24:35 -07:00
David Aguilar bbfa0d8e7d test: add a .gitignore to test/tmp
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-04-24 23:25:13 -07:00
David Aguilar 9ba037aafb Save the splitter dimensions too ;)
This adds the ability to save the splitter sizes on startup/shutdown
by removing responsibility from the models.  We now use a SettingManager
singleton to provide an api for arbitrary data saving.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-04-24 19:34:21 -07:00
David Aguilar e524af1577 settings: add a SettingsManager singleton
The SettingsManager class provides an interface for view
de/serialization.  We'll use this to handle saving gui settings.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-04-24 19:20:17 -07:00
David Aguilar d1075652b9 views: add import/export state methods
This adds export_state() and import_state() methods to the
StandardView class.  Subclasses extend this method to
handle custom view data.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-04-24 19:16:00 -07:00
David Aguilar e586e74b81 cola.model: don't derive from observable
cola.model.Model no longer derives from Observable.
Instead, classes that need subject/observer behavior use
the observable.Observable mixin.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-04-24 19:12:51 -07:00
David Aguilar 912dbff081 git-cola: unconditionally add the bin/ dir to our path
git-cola might not be in our path, so guard against it by placing
cola's bin/ directory in the path.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-04-24 18:59:44 -07:00
David Aguilar e985be1c0c diff/review mode: decorate the window title when in review or diff mode
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-04-24 16:21:57 -07:00
David Aguilar e3004eecab diff/review mode: always use the staged icon and hide modified/untracked items
When reviewing or diffing a branch we should only see things that have been committed
over there.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-04-24 16:11:40 -07:00
David Aguilar 455f394f0d ui: add a Branch->Review action for reviewing in-progress branches
Branch->Review compares a branch against its common ancestor
with the current branch.  This basically shows you what's
in a branch and lets you run difftool on it.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-04-24 03:58:18 -07:00
David Aguilar 0f25ad250b remote: select the current branch by default for 'push'
'git push' now requires both the remote and branch to be specified when pushing,
so select the current branch by default since that is the typical use case.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-04-22 16:02:09 -07:00
David Aguilar 673da3cd3c remotes: always show the status output when doing fetch/push/pull
We now force the status to '1' so that the log display is presented for all
remote operations.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-04-22 16:00:44 -07:00
David Aguilar 33a2f171e4 ui: add a branch->diff menu action to compare against arbitrary revisions
Branch->Diff lets you compare changes against arbitrary commits, tags, branches, etc.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-04-21 03:38:35 -07:00
David Aguilar 421077fafb setup.py: fix a bug when generating .qm files
We were double-adding the .qm extension.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-04-21 02:41:00 -07:00
David Aguilar 75346cde75 Install all cola resources into /usr/share/git-cola
Debian bug #519972 suggested installing all items into
/usr/share/git-cola/* instead of /usr/share/cola.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-04-21 01:23:19 -07:00
David Aguilar 2539c3c113 win32: update the create-installer script for the new directory layout
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-04-20 01:56:47 -07:00
David Aguilar 95f5eedcaf Makefile: make the 'install' target more quiet
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-04-20 01:46:05 -07:00
David Aguilar d08e92e13b difftool: sync up with the last git version of difftool
We keep git-difftool around for compatibility purposes only.
This pulls in vimdiff fixes and support for diffuse.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-04-20 01:36:12 -07:00
David Aguilar 819aacc970 win32: remove a superfluous 'find' command in create-installer.sh
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-04-20 01:27:23 -07:00
David Aguilar e492cd9eb1 qobserver: fix a misspelled QComboBox
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-04-20 01:15:00 -07:00
David Aguilar 6f6f0a1ae1 qtutils: use QApplication.instance() instead of QtGui.qApp
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-04-20 01:13:12 -07:00
David Aguilar f5fed2db85 inotify: terminate the GitNotifier on shutdown
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-04-20 01:13:12 -07:00
David Aguilar 8eb7978376 utils: make fork() take a list instead of a *args parameter
This makes things clearer by using a list as an argument
to utils.fork().  This also centralizes the difftool invocations
in the cola.difftool module.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-04-20 01:13:12 -07:00
David Aguilar 8a4a318f13 Use a private module dir for cola's libraries
This fixes debian bug #519972:
        git-cola: should use private module dirs.

All of the cola modules are now installed into $prefix/share/cola/lib.
The private versions of jsonpickle and simplejson were moved up
a level in the source tree and synced with upstream since they are now
installed into $prefix/share/cola/lib as well.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-04-20 01:13:06 -07:00
David Aguilar 1818d777f9 tests: create all temporary directories underneath test/tmp
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-04-19 21:42:33 -07:00
David Aguilar 52faaf1f00 app: make ColaApplication test-friendly by using less derivation
ColaApplication no longer inherits from QApplication.  It now
contains either a QApplication or QCoreApplication based on the value
of the gui=<bool> construction option.

Consequently, this makes tests work without a gui =)

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-04-19 21:41:54 -07:00
David Aguilar a18b9476eb version: remove the get_version() docstring
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-04-19 00:00:47 -07:00
David Aguilar aea38354d0 difftool: only install git-difftool on older versions of git
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-04-18 23:58:57 -07:00
David Aguilar 6575874d3b version: add a get_version() function to replace version.version
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-04-18 23:58:57 -07:00
David Aguilar 90546dea02 Add a ColaError base exception class
ColaError is now the base of all cola exceptions.

We don't really use exceptions much at the moment, but it's there
for future use.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-04-18 23:57:32 -07:00
David Aguilar e8feffb418 observable: add more source comments
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-04-17 04:01:44 -07:00
David Aguilar 32cf8cefb0 qobserver: minimize imports and avoid double-underscores
This lessens namespace pollution and makes the code more obvious.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-04-17 03:57:25 -07:00
David Aguilar ee3fab5ce2 resources: add more source comments
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-04-17 03:57:25 -07:00
David Aguilar dd43457a4d observer: remove the unused set_debug() method
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-04-17 03:57:25 -07:00
David Aguilar 11e80f8560 settings: add comments to the SettingsModel
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-04-17 03:57:25 -07:00
David Aguilar febf7a6a89 models: use core.write_nointr and other trivial fixups
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-04-17 02:37:37 -07:00
David Aguilar 37e39e1a4e ui: simplify the search and branch menus
Less is more.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-04-17 02:26:19 -07:00
David Aguilar bcb28c1900 model: add more comments and minimize the number of imports
We now import just the 'types' module instead of importing
lots of individual items out of it.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-04-16 02:02:17 -07:00
David Aguilar f01269d901 main: add more comments to the cola.main module
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-04-16 02:01:30 -07:00
David Aguilar c94801d672 tests: add deadlock and interrupted system call tests
This adds tests to ensure that the git.Git command object
never deadlocks and properly handles interrupted system calls.

This also does away with the stdout/stderr.py fixtures
since the code is now contained within each test.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-04-16 02:00:09 -07:00
David Aguilar 2be4692a85 PEP-8: cleanup docstrings and methods in the tests
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-04-16 02:00:09 -07:00
David Aguilar 7cf78db43e git: remove the deprecated with_extended_output option
with_extended_output has been replaced by with_status.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-04-16 02:00:00 -07:00
David Aguilar 3a16abf80d difftool: add support for a difftool.prompt config variable
difftool now supports difftool.prompt so that users do not have to
pass --no-prompt or hit enter each time a diff tool is launched.
The --prompt flag overrides the configuration variable.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-03-30 09:42:47 -07:00
David Aguilar ee2af7186d difftool: add a -y shortcut for --no-prompt
This is another consistency cleanup to make git-difftool's options
match git-mergetool.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-03-30 09:42:47 -07:00
David Aguilar 505144bb8c difftool: use perl built-ins when testing for msys
I don't even know what $COMSPEC means so let's be safe and use the
same perly $^O test add--interactive uses.  While we're at it, make
git-difftool match the prevalent git-perl style.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-03-30 09:42:41 -07:00
David Aguilar 7fd60f3b64 build: cleanup Makefile/setup.py
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-03-29 11:14:08 -05:00
David Aguilar 33e7100411 tests: improve the test_cola_model test cases
test_cola_model now uses its own local model for testing
so that we don't have to maintain one in the test/helper module.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-03-29 11:14:08 -05:00
David Aguilar d3cd74d331 core: add a wait_nointr() function and use {read,wait}_nointr() in cola.git
cola.git's execute() method now uses {read,wait}_nointr() to handle the
"interrupted system call" problem.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-03-29 11:14:08 -05:00
David Aguilar 0452fe48cf refactor: change various modules to use core.{read,write}_nointr
OSX and other systems can interrupt us in the middle of a system call so
wrap all read/write calls with an automatic retry loop.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-03-29 11:14:07 -05:00
David Aguilar 19964a0ec1 core: add read/write_nointr helper functions
read/write_nointr automatically retries interrupted system calls.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-03-29 11:14:07 -05:00
David Aguilar 4b12880b73 git: catch OSError in addition to IOError in the execute() method
Popen.wait() can throw OSError errno.EINTR, so catch it and retry the syscall.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-03-29 11:14:07 -05:00
David Aguilar d04e6d5a05 app: move ColaApplication into its own 'app' module and test it
This refactors things so that ColaApplication lives in the cola.app module.
i18n is tested by the test_cola_app test script.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-03-29 11:14:07 -05:00
David Aguilar 3ab6e07b34 resources: add a 'resources' module for finding cola resource files
resources.path() returns paths relative to the cola installation root.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-03-29 11:13:59 -05:00
David Aguilar 32d9fdb1a9 inotify: add more source comments and cleanup the code
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-03-29 00:10:55 -05:00
David Aguilar 824e751168 tests: add better comments to the test_cola_git test
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-03-28 17:30:18 -05:00
David Aguilar 195561970f tests: add tests for the cola.core module
These test the unicode encode/decode functions in the cola.core module.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-03-28 17:30:18 -05:00
David Aguilar 67d2f3ddaa Makefile: update 'test' target and add a 'coverage' target
The test target no longer adds build/lib to PYTHONPATH.
The 'coverage' target runs nose's coverage plugin.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-03-28 17:30:18 -05:00
David Aguilar c8c3262219 tests: renamed tests to match the prevalent Python coding style
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-03-28 17:30:18 -05:00
David Aguilar 35aaf18c21 cola: allow Ctrl-C to exit cola from the command-line
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-03-28 17:30:18 -05:00
David Aguilar 70614f887c git: handle interrupted system calls on OSX
OSX will actually interrupt creationg of subprocess.Popen objects so catch
the OSError and retry.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-03-28 17:30:18 -05:00
David Aguilar 8980d9cb98 inotify: make the inotify module import-safe
The tests were failing on OSX because inotify wasn't import-safe.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-03-28 17:30:17 -05:00
David Aguilar dfe07d51c9 options: fix unicode bugs in the option dialog
Font names with non-ascii characters were crashing cola.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-03-28 17:30:17 -05:00
David Aguilar 787e46ea1d remotes: warn when non-ffwd is used for push and pull
cola could be destructive when using push/pull w/out fast-forward-only
so issue more warnings.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-03-26 03:23:10 -07:00
David Aguilar f4b8e0479f models: pass the remote/local branch to fetch/push/pull when specified
We're going to hide local branches in the pull dialog (so that pulling
can only happen on the current branch) so prepare the model for the new
caller.  This improves the usability of fetch/push as well.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-03-26 03:22:28 -07:00
David Aguilar a3962781da views/remote: hide appropriate controls when in specific fetch/push/pull modes
This makes the remote gui automatically hide controls based on its mode.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-03-26 03:02:06 -07:00
David Aguilar a138c60dc5 ui/remote: name the local/remote branch labels
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-03-26 03:00:06 -07:00
David Aguilar a3a86efe2d ui: simplify the log display by removing the search functionality
This makes things the interface simpler by removing the rarely-used
search feature in the cola log gui.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-03-19 01:53:25 -07:00
David Aguilar 7f714dc6b2 logging: add support for the cola.showoutput configuration variable
This makes it so that the log window honors cola.showoutput.

    never = never show output
    always = always show the output
    errors = only show output on errors (default)

This was done by centralizing logging logic in qtutils.log() and adding
a model.should_display_log(status) method.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-03-19 01:53:24 -07:00
David Aguilar e99440d645 ui: removed the status bar and cleaned out some dead code
The status bar isn't very useful now that the right-hand tree widget
succinctly displays a file's status, so remove it.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-03-19 01:53:24 -07:00
David Aguilar 312a5a416a gui: improve logging by adding drawers to the main window
The log window is now a drawer positioned at the bottom of the
main window.  This does away with the dreaded log window.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-03-19 01:53:17 -07:00
David Aguilar 2d7934c68c refactor: move log, options, and select commits into separate controllers
This makes the util controller more lightweight separating out the
logger, options, and select_commits functionality into separate
controllers.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-03-19 01:50:34 -07:00
David Aguilar 362cc1b8f5 qtutils: minimize the number of imports
We now access QtGui classes through the QtGui module so that
we import less and are more explicit about our module
dependencies at the call sites.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-03-19 01:50:33 -07:00
David Aguilar 308e1d443d refactor: avoid importing encode and decode directly
This changes call sites so that they always use core.encode/decode
instead of importing those functions directly.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-03-19 01:48:41 -07:00
David Aguilar e02484f060 json: run dos2unix on cola's pickle module
This keeps jsonpickle consistent with the rest of cola.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-03-17 22:00:44 -07:00
David Aguilar 61cff60a69 json: make cola's pickle module exception-safe
cola.jsonpickle wasn't exception-safe and was breaking
help('modules').

This fixes Debian bug #519971.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-03-17 01:03:41 -07:00
David Aguilar fe010d0e29 controllers: warn users when amending a published commit
This makes cola more user-friendly by warning users when
rewriting published history.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-03-13 02:21:52 -07:00
David Aguilar 7f28c9e2fc models: add is_commit_published()
This allows us to check whether our HEAD has already been
published.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-03-13 02:20:42 -07:00
David Aguilar e093aacaf5 models: add get_local_config() and use it when reading gui.diffcontext
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-03-13 02:19:57 -07:00
David Aguilar 9b212f95da models/get_global_config(): add support for default values
We now use get_param() so that we can pass default values to
the get_global_config().  load_commitmsg_template() was updated
accordingly.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-03-13 02:17:15 -07:00
David Aguilar 23000b5a1e models/current_branch(): catch stderr and return '' for detached HEADs
We now return the empty string when our head is detached in
current_branch().  This also improves detached head usability
by capturing the stderr message that was being printed to the
console at startup.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-03-13 02:12:52 -07:00
David Aguilar 5a1a085360 difftool: sync with the proposed git.git patch
This updates the documentation to match the proposed git.git patch.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-02-24 01:31:46 -08:00
David Aguilar 96f6064bd7 difftool: use "wincmd l" when launching vimdiff
Oops, this must have been dropped when syncing with the git.git version.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-02-24 01:30:15 -08:00
David Aguilar bde4e51eec difftool: fix a bug when reading $GIT_MERGE_TOOL
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-02-24 00:52:01 -08:00
David Aguilar e5e325ba95 difftool: use the old variable names for inclusion in git.git
We should probably keep LOCAL/REMOTE since that's consistent with
the rest of git.  This also adds fallbacks for the old mergetool
variables for user convenience.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-02-24 00:16:58 -08:00
David Aguilar d3c0823b21 stash: add a keep_index checkbox to allow 'git stash save --keep-index'
This adds a keep_index checkbox to the stash dialog so that
users can save stashes and and keep the index state.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-02-23 21:32:38 -08:00
David Aguilar c0ff004905 stash: add docstrings to the stash controller
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-02-23 21:31:02 -08:00
David Aguilar eaa7bb0f18 cleanup: remove shebang lines from cola modules
Fedora reported that non-executable python modules should
not have shebang lines:

https://bugzilla.redhat.com/show_bug.cgi?id=461483

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-02-23 20:55:58 -08:00
David Aguilar d7be10284b cola: add support for diff.tool
Now that difftool uses diff.tool instead of merge.tool
we teach cola to use merge.tool.

This also removes all usage of the --tool=<tool> parameter
to git-mergetool and git-difftool since the assumption is
that users will use the appropriate diff/merge.tool config variable.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-02-23 02:48:25 -08:00
David Aguilar d18bcadddf difftool: use a different config namespace then git-mergetool
Users might want to have different settings for merging and
diffing, so difftool should use its own config variables
for setting defaults.  We now use the difftool.* config names
instead of mergetool.*.

We still fallback to merge.tool to make it easier on users
who haven't yet defined the new diff.tool config variable.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-02-23 02:25:23 -08:00
David Aguilar c1e464be6d models: detect modifications in newly-added files
This teaches cola to detect the case where an untracked file is added
and then modified prior to commit.

cola now displays the path as both staged and modified, which matches
git status.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-02-11 04:19:52 -08:00
David Aguilar 0e66ab1788 INSTALL: don't mention simplejson since it's a cola built-in
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-02-07 19:26:00 -08:00
David Aguilar 8c7b88ae60 models: refactor the model api
This makes things "more pythonic" by removing the create()
method.  We let jsonpickle handle serialization stuff so that
the model has to know less about how to translate python
objects into dictionaries.

The next thing to do is factor out the common git properties
and subclass models.Model for different controllers:
	models.search, models.compare, etc.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-02-07 19:23:26 -08:00
David Aguilar bac0eba544 controllers: refactor the init() method out of the controllers api
We no longer use the init() stuff so subclasses just override __init__ now.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-02-07 19:23:26 -08:00
David Aguilar 504012ac09 views: simplify the views api
This removes the init() methods in the view internals and makes
things look a bit more pythonic style-wise.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-02-07 19:23:26 -08:00
David Aguilar 64b728ffcf syntax: refactor the api to be more pythonic
We're removing all of the funny init() methods since they just made
things not-simple-enough.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-02-07 19:23:26 -08:00
David Aguilar 35df8357b4 tests: lesson the buffer sizes in the test_git tests
We probably only need to be bigger than PAGE_SIZE so to catch
deadlock so set our buffers to PAGE_SIZE * 2

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-02-07 19:23:26 -08:00
David Aguilar ee53543939 tests: refactor tests to use a single helper.py file
Renaming moving testlib into helper stopped nose from trying to execute
testlib as a test itself.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-02-07 19:23:26 -08:00
David Aguilar 63a317d948 settings: remove the import checks in the settings module
Now that json is built-in we don't have to check whether or not we
have them available.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-02-07 19:23:26 -08:00
David Aguilar c9c595eb2a cola: add json and jsonpickle to make things easier on our users
This'll make installation easier for users and allow us to remove
more dependencies.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-02-07 19:23:16 -08:00
David Aguilar def8023ace inotify: refactor inotify to simplify its pyinotify usage
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-02-07 17:46:49 -08:00
David Aguilar 2e6a42623b controllers: use absolute imports for better portability
PEP-8 recommends using absolute imports to improve portability with
older versions of Python.  Relative imports can also be ambiguous
so don't use them.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-02-07 17:23:46 -08:00
David Aguilar 71d24ba1d9 controllers/remote: select 'origin' by default if it's available
We used to select blindly select the first remote.
We now default to 'origin' if it exists and fallback to the
first remote otherwise.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-02-07 16:05:07 -08:00
David Aguilar 5a5658ce82 Makefile: run nose with more verbosity, doctests, and test ids
This makes it easier to see what's going on when running 'make test'.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-02-07 15:58:09 -08:00
David Aguilar 44691c5f8a ui/options: use a colspan in the diff context
The local repository options had text fields that were too small because
the diff context field wasn't using a colspan.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-02-07 15:56:26 -08:00
David Aguilar a4d96a1cba compare: hide the file list when comparing a single file
When showing history for a single file then there's no need to
show the file list in the compare view.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-02-07 15:54:21 -08:00
David Aguilar 5ef3b3179e difftool: Don't repeat merge tool candidates
git difftool listed some candidates for mergetools twice, depending on
the environment.

This slightly changes the behavior when both KDE_FULL_SESSION and
GNOME_DESKTOP_SESSION_ID are set at the same time; in such a case
meld is used in favor of kdiff3 (the old code favored kdiff3 in such a
case), but it should not matter in practice.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-02-01 01:49:35 -08:00
Markus Heidelberg 31da27aeb7 difftool: add support for Kompare
Signed-off-by: Markus Heidelberg <markus.heidelberg@web.de>
Acked-by: David Aguilar <davvid@gmail.com>
2009-02-01 01:49:35 -08:00
David Aguilar 891858716d controllers: move all of module __init__ into main in prep for refactoring
We'll be doing some big changes soon, so get everything out of __init__
because we really shouldn't be doing much in there at all.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-01-31 01:40:08 -08:00
David Aguilar a240d57244 bin/git-cola: add build/lib/cola to sys.path to find builtin_version.py
Without this we get vuknown-version when running cola out of its source tree.

Reported-by: Mechazawa <zedside@gmail.com>
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-01-31 01:38:39 -08:00
David Aguilar a5cf5183ce cola/version: capture stderr to avoid printing bogus errors to the shell
We weren't capturing stderr which meant that cola was printing
	fatal: could not describe <sha1>

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-01-30 21:36:10 -08:00
David Aguilar 19b7e9ad9b ui: set the point size to '6' for the cola 'about' dialog
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-01-27 15:02:03 -08:00
David Aguilar 842e96e2ed INSTALL: mention the Windows installer
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-01-26 04:03:32 -08:00
David Aguilar 77072775ca cola.desktop: add 'RevisionControl' to cola's registered categories
This is in compliance with:
http://standards.freedesktop.org/menu-spec/menu-spec-1.0.html#category-registry

Reported-by: shaforostoff <shaforostoff@kde.ru>
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-01-26 00:34:45 -08:00
David Aguilar e07e073b71 win32: add a binary installer for Windows
We can now create binary installers for Windows.
create-installer.sh drives InnoSetup and generates Cola-$VERSION.exe.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-01-26 00:14:16 -08:00
David Aguilar 6aacd26e5f main: add support for '--git-path=<path>'
Launching cola from Windows ends up with an environment where 'git.exe'
is not in the $PATH.  The Windows installer will supply --git-path
which solves this issue.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-01-25 23:41:04 -08:00
David Aguilar 8349bcdaf5 win32/cola: update to support the Windows installer
The 'dirname' function did not properly handle paths with
embedded spaces.  Also, Windows uses git-cola.pyw instead
of the default git-cola startup script.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-01-25 23:39:52 -08:00
David Aguilar 906c079524 git-cola: add support for the win32 installation layout
Windows uses a slightly modified installation layout that does
not include the 'python2.x' directory in its site-packages path.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-01-25 16:29:47 -08:00
David Aguilar e2657c59ad git-difftool: update difftool documentation to include some packaging notes
difftool is now available in cola.git and git.git.
Mention this and recommend usage of the version from git.git.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-01-19 19:09:46 -08:00
David Aguilar c6b12a1442 difftool: change trap condition from SIGINT to INT
Markus Heidelberg provided this patch against git.git's
contrib/difftool.

In 'man 1p trap' there is written:

   "The condition can be EXIT, 0 (equivalent to EXIT), or a signal
    specified using a symbolic name, without the SIG prefix, [...]"

   "Implementations may permit names with the SIG prefix or ignore case
    in signal names as an extension."

So now we do it the POSIX compliant way instead of using an extension.

Signed-off-by: Markus Heidelberg <markus.heidelberg@web.de>
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-01-19 19:07:02 -08:00
David Aguilar fc805e5dd3 git-difftool: incorporate suggestions from the git mailing list
Markus Heidelberg has a number of stylistic improvements to the
difftool documentation and comments.  This also adds usage of the
vim "wincmd" command for positioning the cursor at startup.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-01-18 17:07:22 -08:00
David Aguilar e7a80fee43 windows: make clone and open work reliably on windows
os.spawnv is a nasty beast in windows-land.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-01-18 03:10:41 -08:00
David Aguilar 44ed8ab3bf win32/cola: add Python's path to $PATH
This lets us easily launch python scripts.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-01-18 03:10:41 -08:00
David Aguilar c5b0ecd991 cola: use the new with_stderr flag throughout cola
We now grab stderr only when we really need to do so.
Places that care about stderr but not about the return status
no longer need to grab it into a throw-away variable.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-01-17 02:50:41 -08:00
David Aguilar 5d437b618b git: add a with_stderr option to control whether or not we get stderr
The git command module will only include stderr when the with_stderr
option is set to True.  This separates with_extended_output and with_stderr
so that they do not have overlapping duties.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-01-17 02:50:41 -08:00
David Aguilar 9a1f2a7fa6 models: simplify the model worktree status routine
We no longer post-process the status data to remove unmerged
items from the modified files list -- this is done up front
when unmerged entries are first found.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-01-17 02:44:32 -08:00
David Aguilar 73ecfad626 tests: skip serialization tests when simplejson is not available
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-01-16 21:05:13 -08:00
David Aguilar dc7d7b0edf model: simplify the deserializer by using __import__()
We were using the 'imp' module to for loading modules.
Using __import__() is simpler and more robust.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-01-16 21:04:57 -08:00
David Aguilar 166b38543b git: change with_extended_output to only return status, output
The stderr+stdout handling in the git module was broken because it
could deadlock on older versions of Python.  All of the complication
came from trying to handle both stderr and stdout in the same
function.  We now pass stderr=subprocess.STDOUT to subprocess.Popen
so that things become much simpler.  We only include stderr when
with_extended_output=True, otherwise we ignore it.

This fixes deadlocks on older Pythons and on Mac OSX.
It also seems to speed things up a bit on Linux, which is a plus.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-01-16 18:13:33 -08:00
David Aguilar d1d3b93da3 tests: simplify the import test by using __import__()
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-01-16 18:13:30 -08:00
David Aguilar 369ae9fbc3 inotify: add support for pyinotify 0.8.x
The pyinotify API changed between 0.7.x and 0.8.x.
The cola.inotify module now works with both 0.8.x and as 0.7.x.

We do accomplish this by checking pyinotify.__version__ at runtime
to determine which API to use.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-01-15 21:51:31 -08:00
David Aguilar 761ede3ce3 git-difftool: simplify the windows exe() handling
Windows doesn't need the full path to git.exe.
We just need to exec "git.exe" instead of "git".

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-01-15 21:01:12 -08:00
David Aguilar 37a01c1372 win32: update the 'cola' script so that it passes "$@"
We were losing command-line args when run through the 'cola'
windows wrapper script.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-01-15 20:50:40 -08:00
David Aguilar 273be07870 git-difftool: add portability code for Windows
git-difftool now works on Windows.
The GIT_EXTERNAL_DIFF rewrite broke Windows.
This makes things happy again.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-01-15 20:50:12 -08:00
David Aguilar 789cdd388f git: fix a deadlock on python2.4
Python 2.4's subprocess.Popen requires that you read from stderr before
reading stdout.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-01-15 11:34:06 -08:00
David Aguilar 540bb69c65 ui: add a 'Help->About' menu action
The 'About' dialog lists the cola license and version.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-01-09 02:40:39 -08:00
David Aguilar 35e9fb7e8f Makefile: handle the case where the 'cola' symlink already exists
We should ignore the 'cola' symlink if it already exists.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-01-09 02:28:14 -08:00
David Aguilar 2459017e15 docs: update the 'git difftool' documentation
'git difftool' is much more powerful (and simpler) now,
so update the documentation accordingly.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-01-08 22:06:28 -08:00
David Aguilar 8cfbdc740a search: show author and date in the summary line
The default 'git log' format was updated so that the author
and date are part of the summary format.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-01-07 02:45:42 -08:00
David Aguilar 3c3a08081b git-difftool: simplify the code slightly
Some unnecessary temporary variables and control blocks
were removed.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-01-07 00:58:41 -08:00
David Aguilar b272078cf6 ui: clean up the options dialog
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-01-07 00:58:41 -08:00
David Aguilar 80f22bf71f options: add a cola.tabwidth configuration field
Users can now specify their own tab width in the options dialog.
The setting corresponds to the cola.tabwidth git configuration variable.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-01-06 01:32:39 -08:00
David Aguilar 30a6c67d05 stash: use --index when applying stashes
git stash apply --index preserves the index so we should use it.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-01-06 01:09:05 -08:00
David Aguilar ae43deba34 ui: add a stash button to the main ui
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-01-05 22:40:22 -08:00
David Aguilar bd0f10bd4e usability: reinstate the rescan button
This brings back the old rescan button.  We hide the button whenever
inotify is available since rescan is automatic on linux.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-01-05 22:35:37 -08:00
David Aguilar 95dec436a8 doc: update git links to point to github
Signed-off-by: David Aguilar <davvid@gmail.com>
2009-01-05 19:31:44 -08:00
David Aguilar aed58632e7 git-difftool: take advantage of GIT_EXTERNAL_DIFF
By using GIT_EXTERNAL_DIFF we can make git-difftool support any
option supported by 'git diff'.  This makes git-difftool much more
useful since it can now handle any type of diff,
e.g. worktree vs. index, worktree vs. commit, commit vs. commit, etc.

'git-difftool-helper' is invoked by git and handles all of the
git configuration and merge tool finding logic.

Signed-off-by: David Aguilar <davvid@gmail.com>
2009-01-02 17:20:56 -08:00
David Aguilar 9cde7c554b git-cola: add 'prefix' to the sys.path
We lost the ability to launch git-cola from its source tree when we
fixed things up for Windows.  It would work if you were sitting in
the cola source tree, but not if your current directory was somewhere
else.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-12-30 14:40:35 -08:00
David Aguilar 7715361133 .mailmap: add /me to the mailmap
I had a bad user.name configuration at one point in time, so add
a .mailmap entry to fix it.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-12-29 15:51:58 -08:00
David Aguilar 38be16d8bb win32: vastly simplify the installation procedure for Windows
We now automatically install the bin/cola win32 wrapper script.
This is now the only recommended way to run cola.  Installing
cola on Windows is now no more difficult then unzipping a tarball
and running the 'cola' shell script.

The 'cola' wrapper script is now smarter in that it checks for
both Python2.5 and Python2.6, thus cola can now run with either
version.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-12-29 15:03:27 -08:00
David Aguilar 43d9101681 win32: make gitk launch properly on win32
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-12-29 01:26:09 -08:00
David Aguilar 52a0d1fd07 views: remove the log icon and its toolbar
It doesn't make sense to have a toolbar until we have more buttons
in there.  The 'View Log' functionality was moved to the 'File' menu.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-12-26 20:05:16 -08:00
David Aguilar 5735f747f7 ui: re-save all .ui files using Qt 4.4.2
This updates all of the property definitions to the latest
schema.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-12-26 19:30:23 -08:00
David Aguilar 44a508004d views: remove exec_ calls so that all dialogs are non-modal
Calling exec_ makes it so that the parent window cannot be
raised above the child window.  This removes all exec()
calls to prevent this.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-12-26 19:23:48 -08:00
David Aguilar 5cbaed0f64 search: make the dialog slightly shorter by default
This saves some screen real estate.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-12-26 19:05:59 -08:00
David Aguilar 9bf49779f2 views: move cola.syntax into the views namespace
cola.syntax was moved the cola.views.syntax, since it makes more
sense there.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-12-26 18:16:48 -08:00
David Aguilar 9448492a74 search: pass the parent view to the search dialog
We now pass the parent view to the search dialog so that it can
properly center its window on the screen.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-12-26 18:16:48 -08:00
David Aguilar a542351b8d views: make all dialogs maximizable
Windows requires that we use setWindowFlags on all dialogs
otherwise Qt drops the maximize button.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-12-26 18:16:36 -08:00
David Aguilar 62a7ab1be1 Add a .mailmap file
This fixes a commit where I messed up David's name.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-12-26 16:19:09 -08:00
David Aguilar be70329b11 setup.py: work around the pyuic4 setSortingEnabled bug
We work around the bug by suppressing all lines that mention
'sortingenabled' in the generated .py files.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-12-26 01:29:30 -08:00
David Aguilar 8a1ecb8743 INSTALL: remove references to the old pyuic4 shebang issue
The previous commit works around pyuic4 installations that are
missing shebangs, so we shouldn't mention it in INSTALL anymore.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-12-26 00:16:08 -08:00
David Aguilar 8d8922f52b setup.py: guard against pyuic4 that are missing #!/bin/sh
Some installations of pyqt4 have a copy of pyuic4 that's
missing the shebang line.  Guard against this by using
popen() instead.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-12-26 00:14:04 -08:00
David Aguilar 88ad1302c7 git-difftool: prepend $(git --exec-path) to $PATH
git-sh-setup is no longer in $PATH as of git 1.6.0.
Prepend PATH with git-sh-setup so that git-difftool
can use git-sh-setup.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-12-25 17:20:46 -08:00
David Aguilar fd3a43bef1 remote: add a rebase checkbox to the pull dialog
The pull dialog now has a 'Rebase' checkbox that tells cola
to pass the --rebase option to pull.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-12-25 14:05:12 -08:00
David Aguilar 90e23ce2fd INSTALL: fix a typo in the windows installation section
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-12-25 05:03:08 -08:00
David Aguilar 250282933a win32: add a win32/ folder for storing all of the win32 hacks
Building cola for windows involves a little bit of voodoo.
We now keep all of the windows-specific scripts and such
in the win32/ folder to help make living in windows-land
a little easier.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-12-25 04:30:42 -08:00
David Aguilar 27a7e19a1e INSTALL: remove references to the now unused 'file' command
We no longer mention the 'file' command now that
utils.ident_file_type() uses only mimetypes and filenames
when guessing file types.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-12-24 13:43:16 -08:00
Barry Roberts f18b101db0 Speed up file identification
This is for all platforms and is probably more flexible

Signed-off-by: Barry Roberts <blr@robertsr.us>
2008-12-24 13:40:08 -08:00
David Aguilar 88e9431a62 rebase: list all branches in the branch chooser, not just local ones
The rebase dialog only listed local branches.  It now lists all
branches, including remote ones.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-12-22 08:29:12 -08:00
David Aguilar 3ed716233b models: call 'git update-index --refresh' in get_workdir_state()
There was an earlier version of git that would report files as
being modified when only their modification times changed.
This causes cola to report false-positives in its modified
files list.  We now guard against this by refreshing the index
in cola's get_workdir_state() method.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-12-22 08:12:22 -08:00
David Aguilar 09b4384705 Use 'utf-8' instead of 'utf8' in core.encode()
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-12-19 10:15:44 -08:00
David Aguilar f31d7bd3b9 compare: fix an edge case when decreasing the number of log results
When changing the number of results in the compare dialog down
to a number less than the index of the currently selected
filename then it would generate a traceback.  We now detect
this condition and behave accordingly.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-12-14 14:26:25 -08:00
David Aguilar ddb19ffee5 options dialog: fix some annoyances with the font handling
The font handling in the options dialog had some small
annoyances that have now been fixed.  The dialog should now
show instantaneous results and properly react to all user
input.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-12-14 14:23:50 -08:00
David Aguilar 95bd9b73d7 controllers: fix a typo when restoring the scrollbar values on rescan
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-12-12 11:38:35 -08:00
Barry Roberts cffa5f4894 Context menu items for Commits list
This adds checkout, cherry-pick, and create branch
to the commit list context menu.

Signed-off-by: Barry Roberts <blr@robertsr.us>
2008-12-11 19:07:52 -08:00
David Aguilar 117aa32f12 cola: pass --no-color to 'git diff' to avoid receiving ANSI sequences
If someone has a git config such as:

[color]
	ui = on

Then git might ignore whether a TTY is attached when printing diffs.
We now guard against this by passing --no-color to all 'git diff'
invocations.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-12-11 02:59:22 -08:00
David Aguilar 4c218fe4c1 diff gui: properly restore the scroll bar values on update
We now properly restore the vertical scrollbar values when interactively
staging content for commit.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-12-10 02:56:12 -08:00
David Aguilar e2efc30125 views: fix obsolete references to the old dockwidgets
The main view needed to be updated to reflect the changes to the
main.ui interface file.  This fixes a bug in the stash view.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-12-09 22:26:31 -08:00
Barry Roberts aecfd3dbd9 Fix stash typo
Signed-off-by: Barry Roberts <blr@robertsr.us>
2008-12-09 22:18:08 -08:00
David Aguilar 2612b3a3d9 compare: reinstate the Commit -> Compare File... menu action
We recently lost the ability to compare changes to just a single file
across commits.  This has been reinstated.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-12-09 16:33:26 -08:00
David Aguilar dda2f83805 i18n: fix a few more places where we weren't unicode-safe
The search dialog and the commit browser now properly decode
git's output before handing it off to Qt.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-12-07 22:02:38 -08:00
David Aguilar c8c4838f48 qobserver: fix a bug where we would respond to redundant notifications
When setting a text field Qt triggers a callback.  We catch this callback
and update our model accordingly.  When the model updates, it triggers
another callback which causes a feedback loop that eventually resolves
with the text field getting set twice.  This is problematic since
it makes entering text problematic.

We now detect this situation and stop the notification loop accordingly.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-12-04 23:13:36 -08:00
David Aguilar 1e35239366 win32: Updated INSTALL to mention dirname.exe
The INSTALL file now points users to the cola website for file.exe
and dirname.exe.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-12-04 01:52:41 -08:00
David Aguilar 2e52b5735d models: guard against broken git configs
Users could enter erroneous settings in their .git/config.

For example:

[merge.tool]
	meld

git config --list returns that entry without an "=" sign, which
was crashing cola.  We now ignore these entries.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-12-04 00:57:31 -08:00
David Aguilar 07bd56e25c create branch: fix gui bugs when entering branch names by hand
We were resetting the user input whenever it changed, so we now
only reset it when it was changed as a result of clicking on a
remote branch.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-12-04 00:47:12 -08:00
David Aguilar 28f2099bc2 clone: handle massaging .git strings in URLs when guessing the clone dir
This improves the cola behavior when cloning foo/.git and foo.git URLs.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-12-04 00:13:52 -08:00
David Aguilar a8ce54473c clone: assume that git url = the new destination directory
Previously we used QFileDialog.getSaveFileName() and restricted
users from selecting existing directories.

We now use getExistingDirectory and assume that the destionation
directory should just be the basename of the git url.

We could prompt again, but that would be annoying.  Unfortunately
Qt doesn't have a convenient dialog to allow both existing
directories and new directory names (most of the world is centered
around files, it seems, unlike git which is more dir tree-ish).

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-12-04 00:02:05 -08:00
David Aguilar 3631deb20c controllers: use the --repo option in menu_clone
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-12-03 22:39:54 -08:00
David Aguilar 854e1cb81d opendialog: upddate to use the --repo option
oops, 605a2ba7 changed the option parsing but forgot to update the open dialog
which actually uses it.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-12-03 22:11:22 -08:00
David Aguilar 7a49d47d86 interactive staging: workaround a qt quirk where \r\n is lost in qtextedits
qtextedits unconditionally eat \r\n when returning the selection.
This breaks the interactive staging on files that contain \r\n.
If we're not able to find the selection in the diff then we know
that this has happened so simply replace \n with \r\n and try
again.

This fixes interactive staging on repositories that contain
windows/dos text files.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-11-28 04:43:46 -08:00
David Marti­nez MartÃi dd8cd024e1 search: fix bug that blocks searching
When trying to search something, git-cola complains:
 > AttributeError: Parameter 'commit_list' not available for Model

Attributes for class "Model" must not be null to be detected as a parameter.
Model.get_param_names has the related code to check if it is a parameter
or not.

So, the solution here is initialize model.commit_list with an empty array,
because it is similar to the data used there.

Signed-off-by: David Martínez Martí <deavidsedice@gmail.com>
2008-11-21 00:02:26 -08:00
David Aguilar 6d8bbd16bd models: properly detect merge conflicts with deleted files
When a file is deleted locally and changed on the remote side diff-index
reports the state as 'A' (added) since it's going to add it during the merge.
We now detect this and list it as an unmerged file.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-11-20 12:36:58 -08:00
David Aguilar 0fe0bd3828 inotify: fix a bug introduced when removing the defaults module
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-11-20 02:56:59 -08:00
David Aguilar 6d04408ecc INSTALL: fix a typo in the pyuic4 section
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-11-20 02:46:00 -08:00
Barry Roberts 04def89eb2 Simple blame
Signed-off-by: Barry Roberts <blr@robertsr.us>
2008-11-20 02:31:30 -08:00
David Aguilar 3d0c28af19 gui: fix the gui settings save/restore
The defaults module was removed in the process of refactoring the gui
settings save/restore functionality.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-11-18 03:35:25 -08:00
David Aguilar 72cdd4a7bd main: call git update-index before importing the version module
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-11-18 03:33:31 -08:00
David Aguilar c469d17e63 git: make the git module independent of the exceptions module
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-11-18 03:32:39 -08:00
David Aguilar 82b89d5fe9 core: assume utf-8 when no other encodings are able to decode a string
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-11-18 03:31:47 -08:00
David Aguilar e7b75f26c3 cola: add more documentation strings to the cola modules
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-11-18 03:30:21 -08:00
David Aguilar 58ad4f099a version: remove the git update-index call to improve startup time
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-11-18 02:09:05 -08:00
David Aguilar 8470c27b9d Makefile: add an uninstall target
This target allows users to easily uninstall cola.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-11-16 21:48:11 -08:00
David Aguilar e2437f9605 INSTALL: mention some build issues encountered by users
Issues with pyuic4 and PyQt were added to the INSTALL guide.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-11-16 21:47:34 -08:00
David Aguilar 60675e650b unicode: use core.encode() and core.decode() throughout git-cola
This retrofits git-cola to use the new core encoding module.
This centralizes all of the encoding duties so that we can modify them
as needed.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-11-15 06:16:54 -08:00
David Aguilar c54dfa941f core: add a core module for holding core encoding routines, for now
This creates generic core.encode() and core.decode() routines from the
encoding-guessing patch provided by David Martínez Martí.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-11-15 06:15:19 -08:00
David Martínez Martí 1ef6f5ef63 Guess the correct encoding for each diff line.
Git can handle any type of file, whatever encoding it uses
(or a broken one). Git-cola must not fail (or complain) when
trying to show that data.

Git-cola only handles utf8 in diffs, which is not very useful
when your project has different encodings.

This patch adds a very simple encoding guess for each line.
Do not try to do this at the whole file, because it is useful
when you are viewing a diff of a encode change.

Signed-off-by: David Martínez Martí <deavidsedice@gmail.com>
2008-11-15 05:54:17 -08:00
David Aguilar df58c910be controllers: optimize delete_files by only calling rescan() once
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-11-13 23:04:52 -08:00
David Aguilar af7cc1b059 models: do not list a file in both the unmerged and modified sections
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-11-13 23:04:23 -08:00
David Aguilar eca3d33342 git: check for errno.EINTR when handling interrupted system calls
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-11-13 17:50:47 -08:00
David Aguilar 904c7a681c ui: remove the sortingEnabled property to allow qt 4.1.x compatibility
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-11-13 17:35:51 -08:00
David Aguilar 44f2c3c95d context menu: simplify the context menus in the right-click menu
This simplifies both the code creating the context menus and improves the
order in which items appear in the menu.  This moves the common operations
to the top of each context menu permutation and adds separators to delineate
less common/dangerous operations.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-11-13 00:22:05 -08:00
David Aguilar d70f7230de controllers: disable single and double-click staging for unmerged items
Accidentally staging an unmerged file loses the ability to launch
git mergetool on the unmerged path.  This disables the convenient
staging UI shortcuts for unmerged paths so that someone doesn't
accidentally stage a file they are still merging.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-11-13 00:19:46 -08:00
David Aguilar 8df5059755 models: clear out the unmerged list when checking for updates
The unmerged list was never getting reset, is bad since we'd end
up in an incorrect state.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-11-12 23:42:46 -08:00
David Aguilar 5b2fb776a6 controllers: extend delete_file to delete multiple files
delete_files now respects multiple selection in the status tree.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-11-12 23:36:36 -08:00
David Aguilar 08709bf296 views: fix a bug when selecting an untracked item
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-11-12 23:35:50 -08:00
Barry Roberts 58831dedc0 Add "delete" context menu to untracked files
Signed-off-by: Barry Roberts <blr@robertsr.us>
2008-11-12 23:23:36 -08:00
David Aguilar 2e5c32841c cola: completely rework the cola user interface
git-cola now provides a repository status view that closely matches the
output of 'git status'.

The staged and unstaged lists were replaced by a single tree view.
Likewise, rest of the UI was simplified to account for this --
e.g. there are no more dock widgets in use.

New icons were added for the Staged, Modified, and Unmerged categories.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-11-12 02:47:53 -08:00
David Aguilar 6e8c1357ba views: allow arbitrary input in the combobox and listwidget choosers
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-11-12 02:43:55 -08:00
David Aguilar a55a4486e2 controllers: import new merge messages if they're available
Previously, we would only import new merge messages at startup.
We now 'git hash-object' the merge messages and import new ones
whenever the hash changes.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-11-11 20:24:55 -08:00
David Aguilar 5ea2f4ea88 models: handle the git init case in get_workdir_state()
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-11-07 22:46:06 -08:00
David Aguilar c9c7c26196 models: handle the git init case for removing staged files
When no commit exists we have to fallback to git rm --cached.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-11-07 22:31:37 -08:00
David Aguilar 733651dda6 models: use symbolic-ref instead of git branch in current_branch()
This allows cola to handle both git init and detached heads.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-11-07 22:16:52 -08:00
David Aguilar b251185f1d version: use the current branch when determining the automatic version
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-11-06 21:15:54 -08:00
David Aguilar c2e0fbded9 desktop: updated cola.desktop with the latest changes from debian
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-11-06 21:09:11 -08:00
Barry Roberts 703bbcab28 Fix checkout in create branch
AFAICT, the checkbox was being ignore.

Signed-off-by: Barry Roberts <barry.roberts@xactware.com>
2008-11-05 09:03:55 -08:00
David Aguilar 1dc8343d49 controllers/merge: reformatted code to stay within 80 columns
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-11-04 20:02:00 -08:00
Barry Roberts e59a5f48a4 Disable Commit checkbox when Squash is checked
Signed-off-by: Barry Roberts <blr@robertsr.us>
2008-11-04 19:50:04 -08:00
David Aguilar ba354727f1 INSTALL: update win32 instructions to mention the pyuic4 workaround
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-11-02 19:14:37 -08:00
David Aguilar ac9a0b3c54 git-difftool: don't use "echo -n"
"echo -n" isn't portable.  We now use "printf" instead.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-31 11:50:48 -07:00
David Aguilar af0e7db901 win32: fix the utils.fork() implementation for msysgit
The latest msysgit expects argv to contain "git" even though the argument
to os.spawn is "git.exe".  Interesting.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-30 16:40:42 -07:00
David Aguilar fecc9573da utils: cast to a tuple to properly handle the win32 case
The win32 branch concatenates args with a tuple so we need the cast
so that it does the right thing.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-30 16:26:13 -07:00
David Aguilar 032200b23c branch compare: add a signal/slot to close the branchview dialog
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-30 16:22:42 -07:00
David Aguilar 7a8ac82c54 git.py: retry execute() to workaround interrupted system calls
unix can still kick us with an EINTR so we have to retry read() calls.
This is known to workaround a problem on OSX but it likely exists
other places too.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-29 21:07:44 -07:00
David Aguilar 6d603db101 darwin: remove darwin from the use_shell conditional
we'd like to avoid using the shell at all costs.
If we enable shell=True on darwin then we also have to
shell_quote, but not on win32!  what a tangled mess cross-platform
IPC introduces.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-25 12:21:07 -07:00
David Aguilar 7d38991a13 win32: do not shell_quote commands
shell quoting is a world of hurt so don't do it.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-25 12:07:50 -07:00
David 5a43923d65 git-cola: add support for win32 installation layout
Signed-off-by: David <davvid@gmail.com>
2008-10-25 03:23:30 -07:00
David 419b54bb91 win32: fix subprocess communication
This reverts the non-portable changes to execute()

Signed-off-by: 'David <davvid@gmail.com>
2008-10-25 03:09:57 -07:00
David Aguilar 6ce5fde2c3 repobrowser: pass with_raw_output=True to git cat-file
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-24 13:20:50 -07:00
David Aguilar 97e6c0fc98 repobrowser: fixed bug when calling cat_file
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-24 13:11:23 -07:00
David Aguilar 6c3a174084 version: use git.Git.execute
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-24 03:15:13 -07:00
David Aguilar 7988615f5e Add self.items(), self.iteritems(), __iter__, and __getitem__
This lets you use models like a dictionary.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-23 22:17:36 -07:00
David Aguilar 4266c7e985 git.py: workaround osx interrupted system calls
On osx select gets interrupted and exec'ing out commands fails.
We now retry the system call to recover.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-23 22:16:58 -07:00
David Aguilar 311679776b compare: use get_corresponding_remote_ref instead of get_default_remote
Older versions of git don't let you refer to remotes as refs,
so don't do that.  Newer gits resolve "origin" to "origin/master".

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-23 14:24:26 -07:00
David Aguilar bc3b134088 models: remove the need for parameter registration
Previously, model would only export attributes that it knew about
due to a create() call.  It now determines parameter names dynamically
by filtering __dict__ with some simple rules.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-23 03:53:47 -07:00
David Aguilar e13d8e1fda git-difftool: add a newline after the missing file message
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-22 05:36:57 -07:00
David Aguilar 5e3d981af6 models: fix unicode handling for config load/save
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-22 05:32:55 -07:00
David Aguilar 9e7456d73f models/utils: more unicode fixes
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-22 05:19:10 -07:00
David Aguilar bda4085a24 models: add unicode/i18n support for get_latest_commitmsg()
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-22 05:07:24 -07:00
David Aguilar c1ec81c0dd compare: add a BranchCompareController for use by Branch->Compare
The Branch->Compare dialog lets you compare between your worktree and
arbitrary branches.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-22 04:58:24 -07:00
David Aguilar e02d2c7020 qobserver: add *_item, *_selected and *_index for listwidgets and treewidgets
The model.foo_item now contains the selected item corresponding to the foo
list/tree widgets.  using foo_item vastly simplifies things.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-22 04:57:00 -07:00
David Aguilar 239721256e qobservers: make double-click callback registration automagic
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-22 04:55:44 -07:00
David Aguilar d551b077f9 main: rework the untracked checkbox handling
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-22 04:51:58 -07:00
David Aguilar f4aa12d202 models: slightly refactor the unicode handling
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-22 04:48:13 -07:00
David Aguilar e89e13ea2d views: add a BranchCompareView class
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-22 04:46:00 -07:00
David Aguilar e964d86a2c models: add get_default_remote() and get_corresponding_remote_ref()
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-21 22:40:02 -07:00
David Aguilar 076f7602ec models: some more unicode fixes
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-19 04:40:29 -07:00
David Aguilar 010c63bef4 gitignore: add "tags" to .gitignore
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-19 04:26:25 -07:00
David Aguilar ed9239088b controllers: refactor fetch/push/pull
This removes some duplicated code and makes the GUI better
by removing the tags option from the pull field
(--tags is a fetch and push-only thing).

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-19 04:25:42 -07:00
David Aguilar 06332a62bc startup: instead of bailing out, prompt for a git repo
We used to previously bail out when we were launched from a non-git repo.
This makes it so that we launch an open-directory dialog instead.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-19 04:24:24 -07:00
David Aguilar 3c241a38fc git: rework work_tree / git_dir initialization
git.Git() now contains a _git_cwd entry member which is separate from
_git_dir and _work_tree.  This will let us refactor the way app startup
is handled.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-19 04:22:50 -07:00
David Aguilar 01f0e9c817 difftool launchers: better behavior when amending commits
If you launch git-difftool from the right-click context menus it now takes
the value of the New Commit / Amend Last Commit radio buttons into account
when creating the difftool command.

The biggest difference is that a completely staged file launches with
--commit=HEAD.  When amending, we launch difftool with --commit=HEAD^.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-16 02:12:44 -07:00
David Aguilar 36171c7f31 git-difftool: compare against the index by default for all commands
This reworks git-difftool so that the command invocations are slightly
closer to git-mergetool.  One big difference is that we show things from
oldest to newest when there are changes in both the worktree and the index.

In that case:
	BASE=the latest commit
	LOCAL=the index
	REMOTE=the worktree

Certain diff commands were adjusted so that they would show these three
states in a left-to-right progression since that made most sense to
english speakers.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-16 01:55:11 -07:00
David Aguilar 91717da68a git-difftool: always show local changes in the right-hand pane
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-15 17:25:08 -07:00
David Aguilar f7b7039096 git-difftool: add a check to make sure we don't try to "rmdir ."
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-14 12:55:40 -07:00
David Aguilar a106ef89b3 ui: do not present 'undo changes' and 'stage changes' for unmerged files
We don't want to accidentally stage a file that's unmerged so this removes
the option from the right-click context menu.  We also fixed a bug where we
were launching git-mergetool with '--', which it does not support.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-14 02:49:59 -07:00
David Aguilar c7fe8d046d models: avoid parsing the output of 'git status'
git status is a porcelain so we shouldn't rely on its output.
Change things so that we use diff, diff-index and ls-files to determine
what's changed, staged, unmerged, and untracked.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-14 02:48:32 -07:00
David Aguilar 617320fcb5 git-difftool: better behavior for unmerged files
If a file is created in two branches then it won't exist in the index (yet)
but will be valid for diffing.  git-difftool now handles this case.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-14 02:46:33 -07:00
David Aguilar 8b6ffcd437 unicode: refactor parse_status to centralize more code
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-14 00:17:59 -07:00
David Aguilar 4db4a05d81 cola: improve unicode support
These changes improve unicode support so that commit messages,
etc. can be written in non-english languages.  These changes
make it possible to commit Japanese (and other text) into a
utf-8 project.  We may need to modify things so that we use
the git project's default encoding in the future.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-11 20:48:32 -07:00
David Aguilar abba82d46b commit: fix the 'stage changed files to commit' logic
We were passing in a list of changed files instead of passing
the items in as the args to git.add().  We now do the handle
this correclty.  This also turns on git-add's verbose mode
so that better feedback is given.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-11 13:51:21 -07:00
David Aguilar 031e1711c9 branch compare: do not relaunch difftool when using double-click
If we double-clicked on a file in the file list then we don't
need to relaunch difftool when the 'Ok' button is clicked.
Otherwise, the 'Ok' button launches difftool and closes the
file list.
2008-10-11 13:48:20 -07:00
David Aguilar 88ee7271a9 branch compare: handle double-clicks in the file list
When comparing against another branch you can now double-click
on the file list to launch a comparison against that file.

This is nicer since you can now launch multiple comparisons at once.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-11 13:36:10 -07:00
David Aguilar f267da1e68 ui: simplify the 'commit' and 'branch' menus by adding sub-menus
Less-commonly used and advanced entries have been moved into sub-menus.
This makes things easier to understand and visually cleaner.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-11 13:33:02 -07:00
David Aguilar 1bd06642ec compare: add a file icon to the compare_files list
We now use a file icon to visually indicate that each line in the
compare_files list is a filename.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-11 03:00:25 -07:00
David Aguilar 561d0e04ad compare: restore listwidget selection when performing updates
The 'compare commits' gui now remembers the selected treewidget items and
restores them whenever we change things such as the number of results or
the 'show version numbers' checkbox.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-11 02:49:54 -07:00
David Aguilar 2f59c3d0dc compare: pre-select the latest commit diff by default
We now pre-select the second-to-last commit as the start and the
latest commit as the end commit by default.

This makes it so that the 'compare commits' gui is setup to show the
differences between HEAD^ and HEAD by default.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-11 02:47:22 -07:00
David Aguilar 28bf6fe190 compare: properly display renamed files
We now filter out old renamed file names from the file differences list.
This is better since the only names that are listed are the final,
surviving names in the end revision.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-11 02:20:14 -07:00
David Aguilar d7a1b863c0 git-difftool: detect and handle renamed files
git difftool --start=<rev> --end=<rev> -- <filename> would not show
the correct diff for renamed files.  With this change it now follows renames
and shows the correct diffs.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-11 02:15:01 -07:00
David Aguilar 0fa1305167 tests: rename the git ops test
oops, how did that get the name test_git_opts (vs ops)?

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-11 00:47:03 -07:00
Barry Roberts 7e4b986ff6 actions: add 'get commit message template' to the file menu
This adds the ability to load a commmit template from the file menu.
A default commit template can be set using the commit.template git
config variable.

Signed-off-by: Barry Roberts <blr@robertsr.us>
2008-10-10 22:07:41 -07:00
Matthew E. Levine 8cf225eac1 compare: streamline and improve the compare commits GUI
The compare commits GUI is now simpler and gives users a more
streamlined workflow.  The GUI now shows the file differences
up front and does away with the intermediate file selection dialog.

Signed-off-by: Matthew E. Levine <melevine@disneyanimation.com>
2008-10-10 21:59:20 -07:00
David Aguilar 1b2f2e7894 git-difftool: use empty files for missing files at --start or --end
You can diff files that don't exist in either the starting or ending revision.
We previously bailed out of when that occurred.  We now provide empty files
to diff against since that is more consistent.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-10 21:45:16 -07:00
David Aguilar 8cb1a11126 git-difftool: handle comparing non-existant files
We now properly handle comparing against files that do not exist
in the current checkout.  The current behavior is to show a diff
between an empty file and the file as it existed in the other commit.
Another possibility could be that we just ignore that file altogether...

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-10 00:12:06 -07:00
David Aguilar 4fe9e7b1e9 git-difftool: add a few comments explaining the code's intent
git-difftool was pretty sparsely commented.
These new comments clarify a few small details and don't clutter
an otherwise straightforward script.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-10 00:09:54 -07:00
David Aguilar aff3868566 git-difftool: add better cleanup handling
git-difftool now uses a SIGINT handler to clean up after someone control-c's
out of git-difftool.

The cleanup routines were also improved so that no transient files are ever
left behind.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-09 23:29:59 -07:00
David Aguilar c75a69581a git-difftool: never add changes to the index
The safest thing to do is to never change the state of the index.
This effectively makes git-difftool a read-only viewer, though the user
always has the option of saving to $MERGED if they want to.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-09 23:21:43 -07:00
David Aguilar 2a7cbdea33 gui: streamline the interface and allow more i18n
An overridden tr() method was added to the main controller that
routes things through the main qapp instance.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-10-09 02:28:26 -07:00
David Aguilar 95648aef85 Added cola.gui to setup.py's list of modules
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-26 11:24:33 -07:00
David Aguilar a0d916ced5 compare: make the ok button work in the 'compare files...' selection dialog
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-26 03:10:30 -07:00
David Aguilar e5ed33ca39 compare: add a 'Show Versions' checkbox to control usage of 'git describe'
When 'Show Versions' is enabled the descriptions in the compare view get the
canonical 'git describe' version number prepended to them.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-26 02:56:45 -07:00
David Aguilar 78aa229e0c checkout: show stderr in case of errors
Reported-by: Barry Roberts <blr@robertsr.us>
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-24 00:13:18 -07:00
David Aguilar e3a359a4b0 git-cola: allow running cola from the source tree w/out having to chdir there
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-24 00:12:39 -07:00
David Aguilar 9b31b5e380 gui: refactor code so that all generated code lives in the cola.gui namespace
All of the pyuic4-generated classes now live under cola.gui.*
cola.views is now reserved for subclasses and hand-rolled guis.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-23 22:34:27 -07:00
David Aguilar 86053bd5c8 repobrowser: return the full path to the filename, not just the basename
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-23 17:02:56 -07:00
David Aguilar 1f677be775 doc: update git-difftool documentation for merge.keepBackup
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-23 01:49:36 -07:00
David Aguilar 465151a1c1 git-difftool: use merge.keepBackup to determine wheter to keep *.orig
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-23 01:19:34 -07:00
David Aguilar 7d221cc1d4 ui: tighten up the scrollbars in the dark.qss theme
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-23 01:17:38 -07:00
David Aguilar ec30e1b655 options: properly handle merge.keepbackup
'git config --global --list' mashes all config variables into lowercase
names, so we need to use merge.keepbackup instead of merge.keepbackup.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-22 22:03:33 -07:00
David Aguilar 89c6afd4b7 compare: add a 'Compare File Across Commits...' menu item
With these changes it is now possible to compare commits relative to a
specific commit.  This is basically an extension of the 'Compare Commits'
functionality.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-22 22:02:20 -07:00
David Aguilar 350cbb2f9c doc: updated INSTALL instructions
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-22 01:04:33 -07:00
David Aguilar 94c270a475 style: make the scrollbars slightly smaller in the dark theme
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-20 15:52:40 -07:00
David Aguilar 642d42d23f utils: suppress stdout in run_cmd
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-20 05:05:47 -07:00
David Aguilar 17428c8600 cola: add copyright statements for inclusion in debian
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-20 04:52:31 -07:00
David Aguilar 14ae833897 Add copyright notices to files
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-20 04:48:51 -07:00
David Aguilar de838f024a win32: Launching executables (diff editor, editor, etc.) was busted on Windows
From: Barry Roberts <blr@robertsr.us>
Launching executables (diff editor, editor, etc.) was busted on Windows.
This fixes it.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-20 04:39:24 -07:00
David Aguilar f61a8af01b test: use nose for running tests
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-19 04:20:17 -07:00
David Aguilar f80f6c3e97 ui: add 'Save *.orig Merge Backups' to the options dialog
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-19 04:19:21 -07:00
David Aguilar 471dcc2d63 options: Make merge.keepBackup accessible from the options dialog
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-16 01:55:03 -07:00
David Aguilar 65653bd52e controllers: differentiate between 'Merge Tool' and 'Diff Tool'
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-16 01:54:11 -07:00
David Aguilar 11bbdae878 git-difftool: better support for newly-added files
The documentation was cleaned up a bit too.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-16 01:53:47 -07:00
David Aguilar 4457efd556 ui: reinstate actions from the old 'Repository' menu
We accidentally lost all of the menu actions from the 'Repository' menu
during the last round of refactoring.  This reinstates those actions, though
the respective actions are now in the Commit and Branch menus.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-15 22:43:28 -07:00
David Aguilar 544032b2d6 setup.py: do not replace shebang lines in python scripts
The '#!/usr/bin/env python' is good enough so we can make setup()
leave it alone by changing the regex it uses when searching for shebangs.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-15 02:34:12 -07:00
David Aguilar a25ee8fb60 difftool: fix a bug when creating backups
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-14 18:00:03 -07:00
David Aguilar 2772ccf5e5 ui: added some tweaks to make things look correct on macos
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-12 03:51:49 -07:00
David Aguilar d361e9d64e controllers: added dock window save/restore for the main gui
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-11 05:52:21 -07:00
David Aguilar b0383f243d git-difftool/compare gui: detect when files do not exist in specific revisions
When rev-range mode is on we now check for the existence of directories
and files and handle their presense accordingly.  We now handle creation of
directories that don't exist in the current checkout (do to older ancestry).

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-11 05:51:24 -07:00
David Aguilar 697f97c381 git-cola: $prefix/bin directory at the front of the path
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-11 05:48:27 -07:00
David Aguilar 4aae446066 ui: refine the 'compare commits' dialog
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-11 03:55:03 -07:00
David Aguilar 13db6b3f4c ui: moved "Get Latest Commit Message..." to the file menu
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-11 03:35:41 -07:00
David Aguilar a2af35a47d cola: add a Commit -> Compare Commits menu action
By using Commit -> Compare Commits you can use git mergetools
for showing differences between arbitrary commits.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-11 03:33:12 -07:00
David Aguilar 06a51821a7 controllers: add a compare-commits controller
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-11 03:32:15 -07:00
David Aguilar 267e470f62 views: add a CompareView gui for comparing arbitrary commits
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-11 03:31:31 -07:00
David Aguilar fddb1b47b2 git-difftool: add --start and --end options
If you specify --start and --end git-difftool will compare files between
two arbitrary revisions.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-11 01:46:07 -07:00
David Aguilar 4085731d10 views: call show_current_column() at startup
This makes it so that the column display is initialized at startup instead
of when the user enters the first text into the commit message editor.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-08 22:58:02 -07:00
David Aguilar 268392a290 views: fix edge cases in selected_line()
Selecting the first or last line was not getting properly handled.
This fixes an off-by-one error for the offset and avoids using split
when there is nothing to split.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-08 22:55:36 -07:00
David Aguilar 7358a21e67 build: Add a MANIFEST.in and more doc to share/doc/cola
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-08 00:46:13 -07:00
David Aguilar de607fcac4 version: run 'git update-index --refresh' prior to checking for a dirty state
debian and pypi builds can sometimes get the stat information out of sync
so we guard against it now.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-07 23:10:44 -07:00
David Aguilar 01ccf81e9a version: only consider the master branch when determining versions
Tags are created on the master branch so we should only consider
commits relative to master.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-07 21:53:27 -07:00
David Aguilar ac1a2bd17b ui: show the current column in the commit message editor
This adds a column number label to the commit message editor to show
the column position of the text cursor.  This is useful since the commit
message editor is a WYSIWG editor for commit messages.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-07 21:27:06 -07:00
David Aguilar e89bac12b6 setup.py: install doc/*.txt to share/doc/cola
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-07 21:05:11 -07:00
David Aguilar 535144f6e9 git-difftool: staged_files should not pass a commitish when using --ref
The common case (no changes in the index) was creating a
three-way merge when it should have done just two-way.
This is because --cached doesn't make sense when comparing
against a commitish.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-07 20:50:39 -07:00
David Aguilar 4cf9d9f755 grep: add a goto_grep context menu action
When viewing grep results you can now right-click and select
'Go Here' to launch $EDITOR on the files found in the grep results.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-07 20:31:17 -07:00
David Aguilar e058aded6f models: only allow access to git commands through model.git.<command>
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-07 20:30:05 -07:00
David Aguilar 518b1c6800 doc: updated difftool documentation and external manpage links
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-07 20:15:11 -07:00
David Aguilar 85e892df16 ui: add a Help -> Documentation menu action
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-07 19:38:15 -07:00
David Aguilar 78ca69b3c9 doc: Add documentation for git-cola and git-difftool
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-07 15:28:37 -07:00
David Aguilar 0b1b76a65c controllers: use 'git difftool --no-prompt'
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-07 15:27:31 -07:00
David Aguilar 0c0709d8a2 git-difftool: add a --no-prompt option
By adding --no-prompt we can remove the dependence on xterm
when using 'Launch Merge Tool'.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-07 15:27:07 -07:00
David Aguilar 7929891587 config: removed the need for the 'cola.saveatexit' config variable
cola.saveatexit was removed since cola.savewindowsettings is sufficient.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-07 05:51:35 -07:00
David Aguilar 605a2ba7e9 main: add better handling for the --style option
If users specify a custom stylesheet we now register all
immediate subdirectories with QtCore.QDir.

This makes it possible to use resources and styles outside
of the git-cola installation path.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-07 05:45:20 -07:00
David Aguilar 43e969cad0 git-difftool: modify git-cola to use git-difftool
Lots of cola code is now simpler since we're using our new
custom git-difftool command.

Several config variables changed since there is no longer a
concept of a "diffeditor."  gui.diffeditor was replaced by the
standard merge.tool variable, etc.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-07 05:42:38 -07:00
David Aguilar cd27b8c390 tools: added a git-difftool helper script
git-difftool allows using mergetools for staging changes.
This is used by the 'launch diffeditor' functionality.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-07 04:27:27 -07:00
David Aguilar b3d5278fe5 Makefile: simplify DESTDIR/prefix usage
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-09-02 00:30:24 -07:00
David Aguilar 4d0062648d build: allow relative paths in DESTDIR and prefix
The Makefile now allows any combination of relative
and absolute paths in DESTDIR and prefix.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-08-26 02:39:12 -07:00
David Aguilar ada615378c cola: make the source tree layout similar to the install tree
Consequently, we can now run bin/git-cola from the source tree
without needing run make install.

The tests and bootstrap scripts were updated to reflect this.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-08-26 01:57:22 -07:00
David Aguilar 04d8e81faa cola: add better behavior for 'amend last commit'
The 'Amend Last Commit' radio button now does everything that you
would expect it to do.  The gui now shows the amended status, etc.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-08-25 02:20:36 -07:00
David Aguilar 744fa2e7b6 views: add reset_checkboxes() and is_amend_checked()
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-08-25 02:16:01 -07:00
David Aguilar 0137c2863d models: add machinery for diffing arbitrary revisions
We'll use this later when we implement a gui for viewing
arbitrary diffs.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-08-25 02:11:30 -07:00
David Aguilar 8be9fcd919 ui: remove the last remaining tooltips
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-08-25 01:47:46 -07:00
David Aguilar 33d4581ede build: catch more stuff in the 'make clean' target
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-08-25 01:33:29 -07:00
David Aguilar d01b593e0c tests: use the built cola instead of the installed one
The test suite used to require you to install cola before testing.
The cola tests now use the build/lib directory instead.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-08-24 19:20:04 -07:00
David Aguilar 1c571f3f11 build: remove waf in favor of setuptools
waf is python and it's great but unfortunately most people are
not familiar with it.  Setuptools is the standard Python way of
doing things, so it makes sense to switch.  This makes
packaging simpler too since debian is quite keen at handling
projects that use setuptools.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-08-20 02:57:01 -07:00
David Aguilar 0ac195ec9e git: missing sys import in git.py
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-08-17 16:24:10 -07:00
David Aguilar 2834ea9326 git: pass shell=True on win32
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-08-15 01:39:03 -07:00
David Aguilar a98f5bbaf6 Moved many scripts to the maint branch
This makes things more tidy since we don't have to expose these maintenance
scripts in the main branch.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-08-13 03:26:02 -07:00
David Aguilar 9373b4b924 Move all debian/ files into the release branch
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-08-12 19:38:05 -07:00
David Aguilar e77e501339 Added a script for auto-generating debian/changelog
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-08-11 01:28:31 -07:00
David Aguilar 936c963a90 More gitignoreables
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-08-11 01:01:51 -07:00
David Aguilar e62093e334 ui: fix the 'save window settings' feature
When we removed the main gui's slider we forgot to update
the save/restore methods for the UI.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-08-09 19:12:33 -07:00
David Aguilar 5f2459e5f3 ui: work around a qt 4.1 bug where QObject.tr throws an exception
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-08-06 10:23:32 -07:00
David Aguilar 14d5ec1a93 ui: remove excessive tooltips, fix a small bug
Many of the tooltips were out of date or unnecessary
so they're gone now.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-08-06 02:10:40 -07:00
David Aguilar a75871e6f2 style: use single quotes whenever possible
We were mixing single and double quotes.
Double quotes should only be used when the string itself
contains single quotes.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-08-05 20:51:11 -07:00
David Aguilar 02aaa34f53 ui: centralize some more tr() calls
main.set_info() now handles the translation call so this
patch removes the calls to tr() from the calling side.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-08-05 20:50:17 -07:00
David Aguilar bf7379ffbd ui: make the syntax highlighting colors configurable using a stylesheet
dark.qss now provides an example showing how to set the syntax
highlighting colors.  They colors are properties that are added
to specific widgets.  These properties are queried at runtime to
create the syntax highlighting colors.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-08-05 02:26:59 -07:00
David Aguilar c9c6c4b24b ui: add a checkbox for controlling the status vertical/horizontal layout
this makes the ui less quirky since it no longer tries to guess what
the correct horizontal or vertical setting should be.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-08-03 23:02:41 -07:00
David Aguilar cc945b78f1 cola: added 'undo hunk' and 'undo selection' actions
These actions undo changes to the work tree by calling
'git apply'.  This builds upon the diff selection gui
and lets you manipulate your work tree using the same
interface.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-08-03 22:07:39 -07:00
David Aguilar 1db70deff2 ui: add QRadioButton to the stylesheet
dark.qss now support qradiobuttons

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-08-03 22:06:24 -07:00
David Aguilar c27667033b ui: add a context menu for to staged item list
Some of the options from the unstaged context menu
are now available, notably 'edit' and 'diffedit'.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-08-03 20:59:37 -07:00
David Aguilar d241de811e models: properly report unmerged files
We now detect unmerged files and return the correct diff.
We were previously using --patch-with-raw, which we don't want to
do for unmerged entries since it ends up not giving us a patch ;)

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-08-03 19:35:32 -07:00
David Aguilar fb5242f6c3 controllers: only prompt to import the merge message once
Previously, every time rescan() was called we would prompt the user
asking if they wanted to import the merge message.  We now prompt
once and only once.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-08-03 19:34:47 -07:00
David Aguilar ac46f93007 ui: redo the layout so that we have a central widget
Previously, the main interface shunned having a central
widget in favor of purely having dock widgets in the
dock areas.  Since I haven't figured out how to make
a splittable dock area be the central widget, I made
the status widget the central widget and placed all of
the other windows in the left dock area.

TODO: figure out whether we can have a nice splittable,
dockable layout.  It was possible in qt3, but it does seem
like some of this functionality might not be accessible
from python.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-08-03 19:33:56 -07:00
David Aguilar 1454f2ac07 controllers: add a File -> Clone action
You can now select File -> Clone, enter a URL and
target directory, and a new session of cola is
launched from the clone.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-08-02 22:38:46 -07:00
David Aguilar f933a012fc i18n: centralize some more of the tr() calls
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-08-02 22:37:06 -07:00
David Aguilar 2df7f0163e cola: some small internal cleanup to the startup script
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-08-02 22:30:02 -07:00
David Aguilar 00b8120596 git: fix a python bug in the command module
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-08-01 12:26:27 -07:00
David Aguilar 713291b028 osx: catch subprocess exceptions
For some reason osx gets an interrupted system call
when using subprocess.Popen.  This avoids that problem.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-08-01 02:34:22 -07:00
David Aguilar 40fa4fb82b i18n: update to the latest .po's from git-gui
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-07-31 22:51:58 -07:00
David Aguilar 6f9c58ec6e cola: allow user-supplied stylesheets
Users can now supply --style=/path/to/style.qss
for skinning cola.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-07-31 22:13:18 -07:00
David Aguilar e7fdd63e1a cola: add a --stylesheet=dark option
If you pass --stylesheet=dark then git cola
loads with a cool dark theme.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-07-31 22:06:58 -07:00
David Aguilar 559875897a build: install stylesheets
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-07-31 22:06:26 -07:00
David Aguilar f4a2d7746f gui: add a dark theme stylesheet
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-07-31 22:06:07 -07:00
David Aguilar 205df18df6 commit: report errors when git returns non-zero status
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-07-31 02:00:10 -07:00
David Aguilar 0a44c06db2 Updated README
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-07-30 03:57:50 -07:00
David Aguilar 3678e1619e v1.1.1-1
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-07-30 03:41:13 -07:00
David Aguilar c39e836fe9 configure: update the url to the waf buildsystem
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-07-30 03:19:49 -07:00
David Aguilar c21ad3380f models: gracefully handle various repo types
cola now works on bare repositories and symlinks.
Running cola on a non-git directory will now
print a friendly message instead of a stacktrace.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-07-30 03:16:14 -07:00
David Aguilar 78981d1e74 configure: generate better Makefile vars
We also remove some waf crud about files not being chmod +x
(probably some stupid svn thing or an old waf version).

The makefiles are now portable in that they don't hardcode
the current directory anymore.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-07-30 02:13:48 -07:00
David Aguilar 7c6bf7f4d7 cola: add mergetool support
git-cola now has a mergetool option in the
right-click menu when running on non-macos,
non-windows platforms.

The reason for blocking out windows and macos
is that there is no simple way to spawn a terminal
for the interactive mergetool session.

We could, of course, write our own mergetool wrapper
in Python, but this is the easiest way for now.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-07-29 01:44:00 -07:00
David Aguilar e76dd87d44 cola: use better config values for the font size params
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-07-29 00:47:54 -07:00
David Aguilar 3e97cb744c Fix disconnected widgets on macosx
macos, like windows, doesn't play nice with
disabling the titlebars, so leave them alone there.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-07-27 02:59:26 -07:00
David Aguilar f581fa98ad git-cola: remove some dead code
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-07-26 03:44:03 -07:00
David Aguilar ace2e80cc9 cola: make i18n/l10n work again
Something changed in PyQt where all of the localization
strings had dialog-specific contexts in their names
when passed to qApp.translate().  Since we're using
simple .qm files (without any context) the fix is to
simply blank out the context when calling translate.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-07-23 03:01:33 -07:00
David Aguilar 8c1fc40aed git-python: removed method_missing
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-07-21 00:41:19 -07:00
David Aguilar c63e8c3866 updated readme/license
the gpl is so passe

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-07-21 00:40:51 -07:00
David Aguilar d3e4cb4ea8 Added a COPYRIGHT file and renamed COPYING to LICENSE
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-07-17 03:50:47 -07:00
David Aguilar 6c739e7c58 git: include the GitPython copyright notice
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-07-17 03:43:19 -07:00
David Aguilar 2f9fee66c7 tests: oops, I forgot to update the gitpython test
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-07-17 03:42:47 -07:00
David Aguilar 408e87d75f cola: update cola environment variable names
TODO: cola needs some better documentation.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-07-17 01:56:07 -07:00
David Aguilar c611c3008a debian: remove the python-git dependency
git cola includes its own copy of GitPython's
Git class so there's no need to declare a dependency.

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-07-16 23:19:05 -07:00
David Aguilar 5a9c2cb82e cola: copy selected filenames to the clipboard
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-07-16 23:15:09 -07:00
David Aguilar b59ed2ade9 config: add a cola.editdiffreverse setting
Setting cola.editdiffreverse will cause cola to launch:
	$diffeditor $tmpfile $filename
instead of the default:
	$diffeditor $filename $tmpfile.

$tmpfile points to the contents of:
	git show HEAD:$filename

Signed-off-by: David Aguilar <davvid@gmail.com>
2008-07-16 00:22:14 -07:00
David Aguilar 8ded9009a6 model: add config helper functions for cola+gui items
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-07-16 00:19:11 -07:00
David Aguilar 68bfb55cdb v1.1.0-1
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-07-14 00:55:44 -07:00
David Aguilar b3fc04654c Merge branch 'master' into release
* master:
  README: update the build instructions to mention GitPython
  build: teach the build scripts about the bundled GitPython
  tests: use the interal git module
  cola: switch to using the internal git module
  cola: include GitPython to make installation simpler for users
  syntax: improve the whitespace checker
2008-07-14 00:54:01 -07:00
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 979fcd4211 v1.0.1-7
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-07-09 02:22:30 -07:00
David Aguilar cd6ad92fb5 Merge branch 'master' into release
* master:
  utils: rework fork()
  models: fix (fetch|push|pull)_helper
  style: do the mainstream python thing and use spaces instead of tabs
  syntax: use nicer colors for the diff display
2008-07-09 02:20:23 -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 761314ec49 v1.0.1-5
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-07-01 01:35:19 -07:00
David Aguilar 0dd3e1d666 Merge branch 'master' into release
* master:
  controllers: add support for using the diff editor to compare branches
  ui: add a Branch->Launch Diff Editor Against... menu item
  models: properly detect copied files in parse_status()
  controllers: when restoring selection prefer unstaged files
  ui: add ItemsView and ComboView dialogs
  views: reworked item selection dialogs
  cola: centralized tmpfile management
2008-07-01 01:30:45 -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 f7b7dbba14 v1.0.1-3
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-26 00:15:28 -07:00
David Aguilar 195b9bd01d Merge branch 'master' into release
* master:
  models: update to the latest GitPython API (again)
2008-06-26 00:13:24 -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 c9a104a832 Merge branch 'master' into release
* master:
  scripts: update mktar-win32.sh to use a win32 subdirectory
2008-06-24 02:24:56 -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 688deb1fd1 Merge branch 'master' into release
* master:
  ui: enable the Branch -> Apply Diff From... menu action
2008-06-24 02:23:08 -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 028dcfa038 release: updated debian to cola 1.0.1
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-23 23:52:55 -07:00
David Aguilar 0c9b5e01fc Merge branch 'master' into release
* master:
  controllers: generalized the diff gui for 'Apply Diff From Branch'
  stash: update to the latest GitPython API
  models: add an 'apply_diff_to_worktree' function
  utils: generalize the diff parser
  ui: add an Apply Diffs From.. option to the branch menu
  models: update to the latest GitPython API
  utils: add branch/rev support to the DiffParser class
  models: add branch/rev support to diff_helper
  GitPython: updated to use Govind's latest GitPython changes
2008-06-23 23:51:36 -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 b9343825c7 debian: temp hack until python-git has a deb
Signed-off-by: David Aguilar <davvid@gmail.com>
2008-06-21 01:28:57 -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
240 changed files with 40533 additions and 4810 deletions
+19 -5
View File
@@ -1,7 +1,21 @@
/Makefile
/git-cola.app
/build
/installroot
/.lock-wscript
*.pyo
*.pyc
/build-stamp
/cola/builtin_version.py
/config.mak
/configure-stamp
/dist
/debian/*.log
/debian/files
/debian/bld
/debian/cola
/Meta
/share/locale
/test/tmp
/tags
.DS_Store
.noseids
*.py[co]
*.qm
*.swp
*~
+2
View File
@@ -0,0 +1,2 @@
David Martínez Martí <deavidsedice@gmail.com>
David Aguilar <davvid@gmail.com>
View File
+2
View File
@@ -0,0 +1,2 @@
Copyright (C) 2007, 2008, 2009, 2010, 2011
David Aguilar <davvid@gmail.com> and contributors
+78
View File
@@ -0,0 +1,78 @@
NUTRITIONAL FACTS
-----------------
ACTIVE INGREDIENTS
# apt-get install git-core python python-qt4
Python 2.6 or newer
~ http://python.org/
PyQt 4.4 or newer
~ http://www.riverbankcomputing.co.uk/software/pyqt/download
ADDITIVES
# apt-get install python-pyinotify
pyinotify >= 0.7.1 enables inotify support on Linux
~ http://pyinotify.sourceforge.net/
BREWING INSTRUCTIONS
# apt-get install pyqt4-dev-tools
Normally you can just do "make install" to install git-cola
in your $HOME directory ($HOME/bin, $HOME/lib, etc).
If you want to do a global install you can do
# make prefix=/usr install
You don't need to install cola to run it.
git-cola is designed to run directly out of its source tree.
$ make
$ bin/git-cola
$ bin/git-dag
MAC OS X INSTALLTION INSTRUCTIONS
Whether you install cola yourself with "make install" or
use the "git-cola.app" bundle, you will need to install
Qt and PyQt separately.
The easiest way to do this is to use brew.
~ http://mxcl.github.com/homebrew/
$ brew install git-cola
Once brew has installed git-cola (and its dependencies) you use
git-cola.app or install from source using "make install".
Installing these packages also gives you a PyQt development
environment which can be used for building your own applications
or hacking on cola itself.
WINDOWS INSTALLATION
Download the latest stable Git, Python 2.6, and Py26-PyQt4 installers
http://code.google.com/p/msysgit/
http://python.org/download/
http://www.riverbankcomputing.co.uk/software/pyqt/download
Download the latest git-cola-[version].exe Windows installer
https://github.com/git-cola/git-cola/downloads
Once it's installed you can run it from the Start menu or
from a Git Bash by typing 'cola'.
If you're developing cola on Windows you can run cola out of its source
tree by using the 'win32/cola' shell script.
$ /c/Python26/python setup.py build
$ win32/cola
Note: win32/cola assumes you've installed Python in /c/Python26 or /c/Python25.
Adjust accordingly.
You can also add python to your path and run bin/git-cola directly.
+38
View File
@@ -0,0 +1,38 @@
include bin/git-cola
include bin/git-dag
include cola/*.py
include cola/dag/*.py
include cola/classic/*.py
include cola/controllers/*.py
include cola/main/*.py
include cola/merge/*.py
include cola/models/*.py
include cola/prefs/*.py
include cola/stash/*.py
include cola/views/*.py
include cola/widgets/*.py
include darwin/*
include extras/*.py
include extras/sphinxtogithub/*.py
include test/*.py
include README
include ChangeLog
include COPYING
include Makefile
include INSTALL
include MANIFEST.in
include po/*.po
include share/applications/*.desktop
include share/doc/git-cola/.gitignore
include share/doc/git-cola/*.rst
include share/doc/git-cola/images/*.png
include share/doc/git-cola/_static/*.png
include share/doc/git-cola/_templates/.gitignore
include share/doc/git-cola/*.txt
include share/doc/git-cola/*.conf
include share/doc/git-cola/Makefile*
include share/doc/git-cola/*.perl
include share/doc/git-cola/*.xsl
include share/git-cola/bin/*
include share/git-cola/icons/*
include win32/*
+111
View File
@@ -0,0 +1,111 @@
# The default target of this Makefile is...
all::
# The external commands used by this Makefile are...
GIT = git
NOSETESTS = nosetests
PYTHON = python
TAR = tar
# These values can be overridden on the command-line or via config.mak
prefix = $(HOME)
bindir = $(prefix)/bin
# DESTDIR =
cola_app_base= git-cola.app
cola_app = $(CURDIR)/$(cola_app_base)
cola_version = $(shell env TERM=dummy $(PYTHON) cola/version.py)
cola_dist := cola-$(cola_version)
python_version = $(shell env TERM=dummy $(PYTHON) -c 'import distutils.sysconfig as sc; print(sc.get_python_version())')
python_site := $(prefix)/lib*/python$(python_version)/site-packages
test_flags =
all_test_flags = --with-doctest --exclude=sphinxtogithub $(test_flags)
# User customizations
-include config.mak
all::
$(PYTHON) setup.py build
install: all
$(PYTHON) setup.py --quiet install \
--prefix=$(DESTDIR)$(prefix) \
--install-scripts=$(DESTDIR)$(bindir) \
--force && \
rm -f $(DESTDIR)$(python_site)/git_cola*
rmdir -p $(DESTDIR)$(python_site) 2>/dev/null || true
(cd $(DESTDIR)$(bindir) && \
! test -e cola && ln -s git-cola cola) || true
# Maintainer's dist target
dist:
$(GIT) archive --format=tar --prefix=$(cola_dist)/ HEAD^{tree} | \
gzip -f -9 - >$(cola_dist).tar.gz
doc:
$(MAKE) -C share/doc/git-cola prefix=$(prefix) all
html:
$(MAKE) -C share/doc/git-cola prefix=$(prefix) html
install-doc:
$(MAKE) -C share/doc/git-cola prefix=$(prefix) install
install-html:
$(MAKE) -C share/doc/git-cola prefix=$(prefix) install-html
uninstall:
rm -rf $(DESTDIR)$(prefix)/bin/git-cola \
$(DESTDIR)$(prefix)/bin/git-dag \
$(DESTDIR)$(prefix)/bin/cola \
$(DESTDIR)$(prefix)/share/applications/git-cola.desktop \
$(DESTDIR)$(prefix)/share/applications/git-dag.desktop \
$(DESTDIR)$(prefix)/share/git-cola \
$(DESTDIR)$(prefix)/share/doc/git-cola
rm -f $(DESTDIR)$(prefix)/share/locale/*/LC_MESSAGES/git-cola.mo
rmdir $(DESTDIR)$(prefix)/share/locale/*/LC_MESSAGES 2>/dev/null || true
rmdir $(DESTDIR)$(prefix)/share/locale/* 2>/dev/null || true
rmdir $(DESTDIR)$(prefix)/share/locale 2>/dev/null || true
rmdir $(DESTDIR)$(prefix)/share/doc 2>/dev/null || true
rmdir $(DESTDIR)$(prefix)/share/applications 2>/dev/null || true
rmdir $(DESTDIR)$(prefix)/share 2>/dev/null || true
rmdir $(DESTDIR)$(prefix)/bin 2>/dev/null || true
rmdir $(DESTDIR)$(prefix) 2>/dev/null || true
test: all
$(NOSETESTS) $(all_test_flags)
coverage:
$(NOSETESTS) --with-coverage --cover-package=cola $(all_test_flags)
clean:
$(MAKE) -C share/doc/git-cola clean
find . -name .noseids -print0 | xargs -0 rm -f
find . -name '*.py[co]' -print0 | xargs -0 rm -f
rm -rf build dist tmp tags git-cola.app
rm -rf share/locale
tags:
find . -name '*.py' -print0 | xargs -0 ctags -f tags
pot:
$(PYTHON) setup.py build_pot -N -d .
mo:
$(PYTHON) setup.py build_mo -f
git-cola.app:
mkdir -p $(cola_app)/Contents/MacOS
cp darwin/git-cola $(cola_app)/Contents/MacOS
cp darwin/Info.plist darwin/PkgInfo $(cola_app)/Contents
$(MAKE) prefix=$(cola_app)/Contents/Resources install install-doc
cp darwin/git-cola.icns $(cola_app)/Contents/Resources
app-tarball: git-cola.app
$(TAR) czf git-cola-$(cola_version).app.tar.gz $(cola_app_base)
.PHONY: all install doc install-doc install-html test clean tags
.PHONY: git-cola.app app-tarball
+24 -195
View File
@@ -1,4 +1,8 @@
ugit is Copyright (C) 2007 David Aguilar
git-cola: A highly caffeinated git GUI
git-cola is a simple, powerful, and feature-rich git GUI.
Copyright (C) 2007-2012, David Aguilar and contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -13,202 +17,27 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
REQUIREMENTS
------------
~ git version 1.6.3 or newer
~ Python 2.6 or newer
~ PyQt 4.4 or newer
ABOUT UGIT:
=============================================================================
FORK
----
$ git clone git://github.com/git-cola/git-cola.git
$ xdg-open https://github.com/git-cola/git-cola
ugit: A PyQt-based git gui
DOWNLOAD
--------
~ http://git-cola.github.com/
ugit started out as a PyQt re-implementation of git-gui.
It has an evolving feature set and aims to be a full-featured
companion to the existing git uis.
DISCUSS
-------
~ http://groups.google.com/group/git-cola/
Join, suggest features, and send patches with 'git send-mail'.
DEPENDENCIES:
=============================================================================
python 2.4.x
pyqt 4.3.x
ABOUT THE ICONS:
=============================================================================
staged.png was blatanly stolen from the "slick" KDE icon set.
All other icons were blatanly stolen from the Apache httpd server icon set.
Those icons are covered by the following license:
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.
SUPPORT
-------
~ http://github.com/git-cola/git-cola/issues
-14
View File
@@ -1,14 +0,0 @@
Short-term:
Complete the branch menu
Documentation, homepage
Medium-term:
Options dialog and save/restore:
We can leverage the Model class for this since
it already has a JSON serializer.
JSON is probably a good-enough format for .ugitrc.
Long-term:
OpenGL-based commit/cherry-pick browser?
(think node-based ala Shake or Nuke)
StGit integration?
Executable
+45
View File
@@ -0,0 +1,45 @@
#!/usr/bin/env python
# -*- python-mode -*-
"""Takes care of starting the main function
"""
__copyright__ = """
Copyright (C) 2007-2012 David Aguilar <davvid@gmail.com> and contributors
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.
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.
"""
import os
import sys
def cola_init():
"""Provides access to the cola modules"""
# Try to detect where it is run from and set prefix and the search path.
# It is assumed that the user installed Cola using the --prefix= option
prefix = os.path.dirname(os.path.dirname(
os.path.realpath(os.path.abspath(__file__))))
# Look for modules in the source or install trees
cola_mod = os.path.join(prefix, 'cola', '__init__.py')
if os.path.exists(cola_mod):
# Source tree
sys.path.insert(1, prefix)
else:
# Install tree
install_lib = os.path.join(prefix, 'share', 'git-cola', 'lib')
sys.path.insert(1, install_lib)
if __name__ == '__main__':
# lights, cameras, action
cola_init()
from cola.app import main
main('git-cola')
Executable
+45
View File
@@ -0,0 +1,45 @@
#!/usr/bin/env python
# -*- python-mode -*-
"""git-dag: An advanced git DAG visualizer
"""
__copyright__ = """
Copyright (C) 2007-2012 David Aguilar <davvid@gmail.com> and contributors
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.
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.
"""
import os
import sys
def cola_init():
"""Provides access to the cola modules"""
# Try to detect where it is run from and set prefix and the search path.
# It is assumed that the user installed Cola using the --prefix= option
prefix = os.path.dirname(os.path.dirname(
os.path.realpath(os.path.abspath(__file__))))
# Look for modules in the source or install trees
cola_mod = os.path.join(prefix, 'cola', '__init__.py')
if os.path.exists(cola_mod):
# Source tree
sys.path.insert(1, prefix)
else:
# Install tree
install_lib = os.path.join(prefix, 'share', 'git-cola', 'lib')
sys.path.insert(1, install_lib)
if __name__ == '__main__':
# lights, cameras, action
cola_init()
from cola.app import main
main('git-dag')
-15
View File
@@ -1,15 +0,0 @@
#!/usr/bin/env python
# Copyright (C) 2007, David Aguilar <davvid@gmail.com>
# License: GPL v2 or later
import sys
from PyQt4 import QtCore, QtGui
from ugitlibs.models import GitModel
from ugitlibs.views import GitView
from ugitlibs.controllers 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
@@ -1 +0,0 @@
/.waf-*
-21
View File
@@ -1,21 +0,0 @@
#!/usr/bin/env python
import os
import platform
import Params
def pymod(prefix):
'''Returns a lib/python2.x/site-packages path relative to prefix'''
python_ver = platform.python_version_tuple()
python_ver_str = 'python' + '.'.join(python_ver[:2])
return os.path.join (prefix, 'lib', python_ver_str, 'site-packages')
def configure_python(conf):
if not conf.check_tool ('python'):
Params.fatal ('Error: could not find a Python installation.')
def configure_pyqt(conf):
# pyuic4 is a custom build object, hence the 2nd parameter
if not conf.check_tool ('pyuic4', os.path.dirname (__file__)):
Params.fatal ('Error: missing PyQt4 development tools.\n'
+ 'Hint: on Debian systems try:\n'
+ '\tapt-get install pyqt4-dev-tools python-qt4-dev')
-102
View File
@@ -1,102 +0,0 @@
#!/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 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 the pyuic4 binary')
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}'
cmd_color = 'BLUE'
Action.simple_action ('pyuic4', cmd_template, cmd_color)
# register .ui for use with python
env.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)
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 int(version) < 4:
conf.check_message ('pyuic version', '(too old)', 0,
option= '(%s)' % version)
return False
conf.check_message ('pyuic4 version', '', 1, option='(%s)' % version)
# all tests passed
return True
-314
View File
File diff suppressed because one or more lines are too long
+6
View File
@@ -0,0 +1,6 @@
from cola.cmdfactory import factory
from cola.main.model import model
from cola.models.selection import selection
from cola.models.selection import selection_model
from cola.models.selection import single_selection
from cola.notification import notifier
+294
View File
@@ -0,0 +1,294 @@
# Copyright (C) 2009, David Aguilar <davvid@gmail.com>
"""Provides the main() routine and ColaApplicaiton"""
import glob
import optparse
import os
import signal
import sys
try:
from PyQt4 import QtGui
from PyQt4 import QtCore
from PyQt4.QtCore import SIGNAL
except ImportError:
print >> sys.stderr, 'Sorry, you do not seem to have PyQt4 installed.'
print >> sys.stderr, 'Please install it before using git-cola.'
print >> sys.stderr, 'e.g.: sudo apt-get install python-qt4'
sys.exit(-1)
# Import cola modules
import cola
from cola import cmds
from cola import git
from cola import guicmds
from cola import inotify
from cola import i18n
from cola import qtcompat
from cola import qtutils
from cola import resources
from cola import signals
from cola import utils
from cola import version
from cola.classic import cola_classic
from cola.dag import git_dag
from cola.decorators import memoize
from cola.main.view import MainView
from cola.main.controller import MainController
from cola.widgets import cfgactions
from cola.widgets import startup
def setup_environment():
# Spoof an X11 display for SSH
os.environ.setdefault('DISPLAY', ':0')
# Provide an SSH_ASKPASS fallback
if sys.platform == 'darwin':
os.environ.setdefault('SSH_ASKPASS',
resources.share('bin', 'ssh-askpass-darwin'))
else:
os.environ.setdefault('SSH_ASKPASS',
resources.share('bin', 'ssh-askpass'))
# Setup the path so that git finds us when we run 'git cola'
path_entries = os.environ.get('PATH').split(os.pathsep)
bindir = os.path.dirname(os.path.abspath(__file__))
path_entries.insert(0, bindir)
path = os.pathsep.join(path_entries)
os.environ['PATH'] = path
os.putenv('PATH', path)
@memoize
def instance(argv):
return QtGui.QApplication(list(argv))
# style note: we use camelCase here since we're masquerading a Qt class
class ColaApplication(object):
"""The main cola application
ColaApplication handles i18n of user-visible data
"""
def __init__(self, argv, locale=None, gui=True):
"""Initialize our QApplication for translation
"""
i18n.install(locale)
qtcompat.install()
# Add the default style dir so that we find our icons
icon_dir = resources.icon_dir()
qtcompat.add_search_path(os.path.basename(icon_dir), icon_dir)
# monkey-patch Qt's translate() to use our translate()
if gui:
self._app = instance(tuple(argv))
self._app.setWindowIcon(qtutils.git_icon())
self._translate_base = QtGui.QApplication.translate
QtGui.QApplication.translate = self.translate
else:
self._app = QtCore.QCoreApplication(argv)
self._translate_base = QtCore.QCoreApplication.translate
QtCore.QCoreApplication.translate = self.translate
# Register model commands
cmds.register()
# Make file descriptors binary for win32
utils.set_binary(sys.stdin)
utils.set_binary(sys.stdout)
utils.set_binary(sys.stderr)
def translate(self, domain, txt):
"""
Translate strings with gettext
Supports @@noun/@@verb specifiers.
"""
trtxt = i18n.gettext(txt)
if trtxt[-6:-4] == '@@': # handle @@verb / @@noun
trtxt = trtxt[:-6]
return trtxt
def activeWindow(self):
"""Wrap activeWindow()"""
return self._app.activeWindow()
def exec_(self):
"""Wrap exec_()"""
return self._app.exec_()
def parse_args(context):
parser = optparse.OptionParser(usage='%prog [options]')
# We also accept 'git cola version'
parser.add_option('-v', '--version',
help='Show cola version',
dest='version',
default=False,
action='store_true')
# Accept git cola --classic
parser.add_option('--classic',
help='Launch cola classic',
dest='classic',
default=False,
action='store_true')
# Specifies a git repository to open
parser.add_option('-r', '--repo',
help='Specifies the path to a git repository.',
dest='repo',
metavar='PATH',
default=os.getcwd())
# Specifies that we should prompt for a repository at startup
parser.add_option('--prompt',
help='Prompt for a repository before starting the main GUI.',
dest='prompt',
action='store_true',
default=False)
# Used on Windows for adding 'git' to the path
parser.add_option('-g', '--git-path',
help='Specifies the path to the git binary',
dest='git',
metavar='PATH',
default='')
if context == 'git-dag':
parser.add_option('-c', '--count',
help='Number of commits to display.',
dest='count',
type='int',
default=1000)
return parser.parse_args()
def process_args(opts, args):
if opts.version or (args and args[0] == 'version'):
# Accept 'git cola --version' or 'git cola version'
print 'cola version', version.version()
sys.exit(0)
if opts.git:
# Adds git to the PATH. This is needed on Windows.
path_entries = os.environ.get('PATH', '').split(os.pathsep)
path_entries.insert(0, os.path.dirname(opts.git))
os.environ['PATH'] = os.pathsep.join(path_entries)
# Bail out if --repo is not a directory
repo = os.path.realpath(opts.repo)
if not os.path.isdir(repo):
print >> sys.stderr, "fatal: '%s' is not a directory. Consider supplying -r <path>.\n" % repo
sys.exit(-1)
# We do everything relative to the repo root
os.chdir(opts.repo)
return repo
def main(context):
"""Parses the command-line arguments and starts git-cola
"""
setup_environment()
opts, args = parse_args(context)
repo = process_args(opts, args)
# Allow Ctrl-C to exit
signal.signal(signal.SIGINT, signal.SIG_DFL)
# Initialize the app
app = ColaApplication(sys.argv)
# Ensure that we're working in a valid git repository.
# If not, try to find one. When found, chdir there.
model = cola.model()
valid = model.set_worktree(repo) and not opts.prompt
while not valid:
startup_dlg = startup.StartupDialog(app.activeWindow())
gitdir = startup_dlg.find_git_repo()
if not gitdir:
sys.exit(-1)
valid = model.set_worktree(gitdir)
# Finally, go to the root of the git repo
os.chdir(model.git.worktree())
# Show the GUI
if opts.classic:
view = cola_classic(update=False)
elif context == 'git-cola':
view = MainView(model, qtutils.active_window())
ctl = MainController(model, view)
elif context == 'git-dag':
ctl = git_dag(model, opts=opts, args=args)
view = ctl.view
# Install UI wrappers for command objects
cfgactions.install_command_wrapper()
guicmds.install_command_wrapper()
# Show the view and start the main event loop
view.show()
# Make sure that we start out on top
view.raise_()
# Scan for the first time
task = _start_update_thread(model)
# Start the inotify thread
inotify.start()
msg_timer = QtCore.QTimer()
msg_timer.setSingleShot(True)
msg_timer.connect(msg_timer, SIGNAL('timeout()'), _send_msg)
msg_timer.start(0)
# Start the event loop
result = app.exec_()
# All done, cleanup
inotify.stop()
QtCore.QThreadPool.globalInstance().waitForDone()
pattern = cola.model().tmp_file_pattern()
for filename in glob.glob(pattern):
os.unlink(filename)
sys.exit(result)
return ctl, task
def _start_update_thread(model):
"""Update the model in the background
git-cola should startup as quickly as possible.
"""
class UpdateTask(QtCore.QRunnable):
def run(self):
model.update_status(update_index=True)
# Hold onto a reference to prevent PyQt from dereferencing
task = UpdateTask()
QtCore.QThreadPool.globalInstance().start(task)
return task
def _send_msg():
if git.GIT_COLA_TRACE == 'trace':
msg = ('info: Trace enabled. '
'Many of commands reported with "trace" use git\'s stable '
'"plumbing" API and are not intended for typical '
'day-to-day use. Here be dragons')
cola.notifier().broadcast(signals.log_cmd, 0, msg)
+135
View File
@@ -0,0 +1,135 @@
# Copyright (c) 2009 David Aguilar
"""Provides a serializable data container"""
import types
import copy
from cola.compat import set
class BaseModel(object):
def param_names(self, export=False):
"""Returns a list of serializable attribute names.
>>> m = BaseModel()
>>> m._question = 'unknown'
>>> m.answer = 42
>>> m.param_names()
['answer']
>>> m.param_names(export=True)
['_question', 'answer']
"""
names = []
for k, v in self.__dict__.iteritems():
if is_atom(v) or is_dict(v) or is_seq(v):
if k.strip('_') != k:
if not export:
continue
names.append(k)
names.sort()
return names
def has_param(self,param):
"""Returns true if a parameter exists in a model.
>>> m = BaseModel()
>>> m.answer = 42
>>> m.has_param('answer')
True
>>> m.has_param('question')
False
"""
return param in self.__dict__
def param(self, param, default=None):
"""Returns the value of a model parameter.
>>> m = BaseModel()
>>> m.answer = 42
>>> m.param('answer')
42
>>> m.param('another answer', 42)
42
"""
return self.__dict__.get(param, default)
def __getattr__(self, param):
"""Provides set_<attribute>(value) append methods.
This provides automatic convenience methods for handling
setattrs with notification.
>>> m = BaseModel()
>>> m.answer = 42
>>> m.set_answer(41)
>>> # Observers are notified
>>> m.answer
41
"""
# Base case: we actually have this param
if param in self.__dict__:
return getattr(self, param)
# Return a closure over param for calling set_param;
# Concrete classes subclass set_param to provide notification
if param.startswith('set_'):
return lambda v: self.set_param(param[4:], v)
errmsg = ("'%s' object has no attribute '%s'" %
(self.__class__.__name__, param))
raise AttributeError(errmsg)
def set_param(self, param, value):
"""Wrapper around setattr()
>>> m = BaseModel()
>>> m.answer = 41
>>> m.set_param('answer', 42)
>>> m.answer
42
"""
setattr(self, param, value)
def copy_params(self, model, params_to_copy=None):
"""Copies params from one model to another.
>>> m = BaseModel()
>>> m.answer = 42
>>> m._question = 'unknown'
>>> m2 = BaseModel()
>>> m2.copy_params(m)
>>> m2._question
'unknown'
>>> m2.answer
42
"""
# Loop over all attributes and copy them over
for k in params_to_copy or model.param_names(export=True):
self.__dict__[k] = copy.copy(model.__dict__[k])
def is_dict(item):
return type(item) is types.DictType
def is_seq(item):
return (type(item) is types.ListType or
type(item) is types.TupleType or
type(item) is set)
def is_atom(item):
return(item is None or
type(item) in types.StringTypes or
type(item) is types.BooleanType or
type(item) is types.IntType or
type(item) is types.LongType or
type(item) is types.FloatType or
type(item) is types.ComplexType)
+123
View File
@@ -0,0 +1,123 @@
"""Provides BookmarksDialog."""
import os
import sys
from PyQt4 import QtGui
from PyQt4 import QtCore
from PyQt4.QtCore import SIGNAL
from cola import core
from cola import qtutils
from cola import settings
from cola import utils
from cola.views import standard
from cola.widgets import defs
def manage_bookmarks():
dlg = BookmarksDialog(qtutils.active_window())
dlg.show()
dlg.exec_()
return dlg
class BookmarksDialog(standard.Dialog):
def __init__(self, parent):
standard.Dialog.__init__(self, parent)
self.model = settings.Settings()
self.resize(494, 238)
self.setWindowTitle(self.tr('Bookmarks'))
self.setWindowModality(QtCore.Qt.WindowModal)
self.layt = QtGui.QVBoxLayout(self)
self.layt.setMargin(defs.margin)
self.layt.setSpacing(defs.spacing)
self.bookmarks = QtGui.QListWidget(self)
self.bookmarks.setAlternatingRowColors(True)
self.bookmarks.setSelectionMode(QtGui.QAbstractItemView
.ExtendedSelection)
self.layt.addWidget(self.bookmarks)
self.button_layout = QtGui.QHBoxLayout()
self.open_button = QtGui.QPushButton(self)
self.open_button.setText(self.tr('Open'))
self.open_button.setIcon(qtutils.open_icon())
self.button_layout.addWidget(self.open_button)
self.add_button = QtGui.QPushButton(self)
self.add_button.setText(self.tr('Add'))
self.add_button.setIcon(qtutils.icon('add.svg'))
self.button_layout.addWidget(self.add_button)
self.delete_button = QtGui.QPushButton(self)
self.delete_button.setText(self.tr('Delete'))
self.delete_button.setIcon(qtutils.discard_icon())
self.delete_button.setEnabled(False)
self.button_layout.addWidget(self.delete_button)
self.button_layout.addStretch()
self.save_button = QtGui.QPushButton(self)
self.save_button.setText(self.tr('Save'))
self.save_button.setIcon(qtutils.save_icon())
self.save_button.setEnabled(False)
self.button_layout.addWidget(self.save_button)
self.close_button = QtGui.QPushButton(self)
self.close_button.setText(self.tr('Close'))
self.button_layout.addWidget(self.close_button)
self.layt.addLayout(self.button_layout)
self.connect(self.open_button, SIGNAL('clicked()'), self.open_repo)
self.connect(self.add_button, SIGNAL('clicked()'), self.add)
self.connect(self.delete_button, SIGNAL('clicked()'), self.delete)
self.connect(self.save_button, SIGNAL('clicked()'), self.save)
self.connect(self.close_button, SIGNAL('clicked()'), self.accept)
self.connect(self.bookmarks, SIGNAL('itemSelectionChanged()'),
self.item_selection_changed)
self.update_bookmarks()
def update_bookmarks(self):
self.bookmarks.clear()
self.bookmarks.addItems(self.model.bookmarks)
def selection(self):
return qtutils.selection_list(self.bookmarks, self.model.bookmarks)
def item_selection_changed(self):
has_selection = bool(self.selection())
self.delete_button.setEnabled(has_selection)
def save(self):
"""Saves the bookmarks settings and exits"""
self.model.save()
qtutils.information("Bookmarks Saved")
self.save_button.setEnabled(False)
def add(self):
path, ok = qtutils.prompt('Path to git repository:',
title='Enter Git Repository',
text=core.decode(os.getcwd()))
if not ok:
return
self.model.bookmarks.append(path)
self.update_bookmarks()
self.save()
def open_repo(self):
"""Opens a new git-cola session on a bookmark"""
for repo in self.selection():
utils.fork([sys.executable, sys.argv[0], '--repo', repo])
def delete(self):
"""Removes a bookmark from the bookmarks list"""
selection = self.selection()
if not selection:
return
for repo in selection:
self.model.remove_bookmark(repo)
self.update_bookmarks()
self.save_button.setEnabled(True)
+2
View File
@@ -0,0 +1,2 @@
from cola.classic.controller import cola_classic
from cola.classic.controller import widget as classic_widget
+84
View File
@@ -0,0 +1,84 @@
from PyQt4 import QtGui
from PyQt4 import QtCore
from PyQt4.QtCore import SIGNAL
import cola
import cola.utils
import cola.difftool
from cola import gitcmds
from cola import qtutils
from cola import signals
from cola.controllers.selectcommits import select_commits
from cola.classic.view import RepoDialog
from cola.classic.model import GitRepoModel
from cola.classic.model import GitRepoEntryManager
from cola.compat import set
def widget(parent, update=True):
"""Return a widget for immediate use."""
view = RepoDialog(parent, update=update)
view.tree.setModel(GitRepoModel(view.tree))
ctl = ClassicController(view.tree)
return view
def cola_classic(update=True):
"""Launch a new cola classic session."""
view = widget(qtutils.active_window(), update=update)
view.show()
return view
class ClassicController(QtCore.QObject):
def __init__(self, view=None):
QtCore.QObject.__init__(self, view)
self.model = cola.model()
self.view = view
self.updated = set()
self.connect(view, SIGNAL('history(QStringList)'),
self.view_history)
self.connect(view, SIGNAL('expanded(QModelIndex)'),
self.query_model)
self.connect(view, SIGNAL('difftool_predecessor'),
self.difftool_predecessor)
def view_history(self, entries):
"""Launch the configured history browser path-limited to entries."""
entries = map(unicode, entries)
cola.notifier().broadcast(signals.visualize_paths, entries)
def query_model(self, model_index):
"""Update information about a directory as it is expanded."""
item = self.view.item_from_index(model_index)
path = item.path
if path in self.updated:
return
self.updated.add(path)
GitRepoEntryManager.entry(path).update()
entry = GitRepoEntryManager.entry
for row in xrange(item.rowCount()):
path = item.child(row, 0).path
entry(path).update()
def difftool_predecessor(self, paths):
"""Prompt for an older commit and launch difftool against it."""
args = ['--'] + paths
revs, summaries = gitcmds.log_helper(all=True, extra_args=args)
commits = select_commits('Select Previous Version',
revs, summaries, multiselect=False)
if not commits:
return
commit = commits[0]
cola.difftool.launch([commit, '--'] + paths)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
cola_classic()
result = app.exec_()
QtCore.QThreadPool.globalInstance().waitForDone()
sys.exit(result)
+433
View File
@@ -0,0 +1,433 @@
import os
import time
from PyQt4 import QtCore
from PyQt4 import QtGui
from PyQt4.QtCore import Qt
from PyQt4.QtCore import SIGNAL
import cola
from cola import gitcfg
from cola import core
from cola import utils
from cola import qtutils
from cola import version
from cola import resources
from cola.compat import set
# Custom event type for GitRepoInfoEvents
INFO_EVENT_TYPE = QtCore.QEvent.User + 42
class Columns(object):
"""Defines columns in the classic view"""
NAME = 'name'
STATUS = 'status'
AGE = 'age'
MESSAGE = 'message'
WHO = 'who'
ALL = (NAME, STATUS, AGE, MESSAGE, WHO)
class GitRepoModel(QtGui.QStandardItemModel):
"""Provides an interface into a git repository for browsing purposes."""
def __init__(self, parent):
QtGui.QStandardItemModel.__init__(self, parent)
self._interesting_paths = self._get_paths()
self._known_paths = set()
self.connect(self, SIGNAL('updated'), self._updated_callback)
model = cola.model()
model.add_message_observer(model.message_updated,
self._model_updated)
self._dir_rows = {}
self.setColumnCount(len(Columns.ALL))
for idx, header in enumerate(Columns.ALL):
self.setHeaderData(idx, Qt.Horizontal,
QtCore.QVariant(self.tr(header.title())))
self._direntries = {'': self.invisibleRootItem()}
self._initialize()
def _create_column(self, col, path):
"""Creates a StandardItem for use in a treeview cell."""
# GitRepoNameItem is the only one that returns a custom type(),
# so we use to infer selections.
if col == Columns.NAME:
return GitRepoNameItem(path)
return GitRepoItem(col, path)
def _create_row(self, path):
"""Return a list of items representing a row."""
return [self._create_column(c, path) for c in Columns.ALL]
def _add_file(self, parent, path, insert=False):
"""Add a file entry to the model."""
# Create model items
row_items = self._create_row(path)
# Use a standard file icon for the name field
row_items[0].setIcon(qtutils.file_icon())
if not insert:
# Add file paths at the end of the list
parent.appendRow(row_items)
self.entry(path).update_name()
self._known_paths.add(path)
return
# Entries exist so try to find an a good insertion point
done = False
for idx in xrange(parent.rowCount()):
child = parent.child(idx, 0)
if child.rowCount() > 0:
continue
if path < child.path:
parent.insertRow(idx, row_items)
done = True
break
# No adequate place found so simply append
if not done:
parent.appendRow(row_items)
self.entry(path).update_name()
self._known_paths.add(path)
def add_directory(self, parent, path):
"""Add a directory entry to the model."""
# Create model items
row_items = self._create_row(path)
# Use a standard directory icon
row_items[0].setIcon(qtutils.dir_icon())
# Insert directories before file paths
row = self._dir_rows.setdefault(parent, 0)
parent.insertRow(row, row_items)
self._dir_rows[parent] += 1
# Update the 'name' column for this entry
self.entry(path).update_name()
self._known_paths.add(path)
return row_items[0]
def path_is_interesting(self, path):
"""Return True if path has a status."""
return path in self._interesting_paths
def _get_paths(self):
"""Return paths of interest; e.g. paths with a status."""
model = cola.model()
paths = set(model.staged + model.unstaged)
return cola.utils.add_parents(paths)
def _model_updated(self):
"""Observes model changes and updates paths accordingly."""
self.emit(SIGNAL('updated'))
def _updated_callback(self):
old_paths = self._interesting_paths
new_paths = self._get_paths()
for path in new_paths.union(old_paths):
if path not in self._known_paths:
continue
self.entry(path).update()
self._interesting_paths = new_paths
def _initialize(self):
"""Iterate over the cola model and create GitRepoItems."""
for path in cola.model().everything():
self.add_file(path)
def add_file(self, path, insert=False):
"""Add a file to the model."""
dirname = utils.dirname(path)
if dirname in self._direntries:
parent = self._direntries[dirname]
else:
parent = self._create_dir_entry(dirname, self._direntries)
self._direntries[dirname] = parent
self._add_file(parent, path, insert=insert)
def _create_dir_entry(self, dirname, direntries):
"""
Create a directory entry for the model.
This ensures that directories are always listed before files.
"""
entries = dirname.split('/')
curdir = []
parent = self.invisibleRootItem()
curdir_append = curdir.append
self_add_directory = self.add_directory
for entry in entries:
curdir_append(entry)
path = '/'.join(curdir)
if path in direntries:
parent = direntries[path]
else:
grandparent = parent
parent = self_add_directory(grandparent, path)
direntries[path] = parent
return parent
def entry(self, path):
"""Return the GitRepoEntry for a path."""
return GitRepoEntryManager.entry(path)
class GitRepoEntryManager(object):
"""
Provides access to static instances of GitRepoEntry and model data.
"""
static_entries = {}
@classmethod
def entry(cls, path, _static_entries=static_entries):
"""Return a static instance of a GitRepoEntry."""
try:
e = _static_entries[path]
except KeyError:
e = _static_entries[path] = GitRepoEntry(path)
return e
class TaskRunner(object):
"""Manages QRunnable task instances to avoid python's garbage collector
When PyQt stops referencing a QRunnable instance Python cleans it up
which leads to segfaults, e.g. the dreaded "C++ object has gone away".
This class keeps track of tasks and cleans up references to them as they
complete.
"""
singleton = None
@classmethod
def instance(cls):
if cls.singleton is None:
cls.singleton = TaskRunner()
return cls.singleton
def __init__(self):
self.tasks = set()
self.threadpool = QtCore.QThreadPool.globalInstance()
self.notifier = QtCore.QObject()
self.notifier.connect(self.notifier, SIGNAL('task_done'), self.task_done)
def run(self, task):
self.tasks.add(task)
self.threadpool.start(task)
def task_done(self, task):
if task in self.tasks:
self.tasks.remove(task)
def cleanup_task(self, task):
self.notifier.emit(SIGNAL('task_done'), task)
class GitRepoEntry(QtCore.QObject):
"""
Provides asynchronous lookup of repository data for a path.
Emits signal names matching those defined in Columns.
"""
def __init__(self, path):
QtCore.QObject.__init__(self)
self.path = path
def update_name(self):
"""Emits a signal corresponding to the entry's name."""
# 'name' is cheap to calculate so simply emit a signal
self.emit(SIGNAL(Columns.NAME), utils.basename(self.path))
if '/' not in self.path:
self.update()
def update(self):
"""Starts a GitRepoInfoTask to calculate info for entries."""
# GitRepoInfoTask handles expensive lookups
task = GitRepoInfoTask(self.path)
TaskRunner.instance().run(task)
def event(self, e):
"""Receive GitRepoInfoEvents and emit corresponding Qt signals."""
if e.type() == INFO_EVENT_TYPE:
e.accept()
self.emit(SIGNAL(e.signal), *e.data)
return True
return QtCore.QObject.event(self, e)
# Support older versions of PyQt
if version.check('pyqt_qrunnable', QtCore.PYQT_VERSION_STR):
QRunnable = QtCore.QRunnable
else:
class QRunnable(object):
pass
class GitRepoInfoTask(QRunnable):
"""Handles expensive git lookups for a path."""
def __init__(self, path):
QRunnable.__init__(self)
self.path = path
self._cfg = gitcfg.instance()
self._data = {}
def data(self, key):
"""
Return git data for a path.
Supported keys are 'date', 'message', and 'author'
"""
if not self._data:
log_line = cola.model().git.log('-1', '--', self.path,
M=True,
all=True,
no_color=True,
pretty='format:%ar%x01%s%x01%an')
if log_line:
log_line = core.decode(log_line)
date, message, author = log_line.split(chr(0x01), 2)
self._data['date'] = date
self._data['message'] = message
self._data['author'] = author
else:
self._data['date'] = self.date()
self._data['message'] = '-'
self._data['author'] = self._cfg.get('user.name', 'unknown')
return self._data[key]
def name(self):
"""Calculate the name for an entry."""
return utils.basename(self.path)
def date(self):
"""
Returns a relative date for a file path.
This is typically used for new entries that do not have
'git log' information.
"""
encpath = core.encode(self.path)
st = os.stat(encpath)
elapsed = time.time() - st.st_mtime
minutes = int(elapsed / 60.)
if minutes < 60:
return '%d minutes ago' % minutes
hours = int(elapsed / 60. / 60.)
if hours < 24:
return '%d hours ago' % hours
return '%d days ago' % int(elapsed / 60. / 60. / 24.)
def status(self):
"""Return the status for the entry's path."""
model = cola.model()
unmerged = utils.add_parents(set(model.unmerged))
modified = utils.add_parents(set(model.modified))
staged = utils.add_parents(set(model.staged))
untracked = utils.add_parents(set(model.untracked))
upstream_changed = utils.add_parents(set(model.upstream_changed))
if self.path in unmerged:
return (resources.icon('sigil-unmerged.png'),
qtutils.tr('Unmerged'))
if self.path in modified and self.path in staged:
return (resources.icon('sigil-partial.png'),
qtutils.tr('Partially Staged'))
if self.path in modified:
return (resources.icon('sigil-modified.png'),
qtutils.tr('Modified'))
if self.path in staged:
return (resources.icon('sigil-staged.png'),
qtutils.tr('Staged'))
if self.path in upstream_changed:
return (resources.icon('sigil-upstream.png'),
qtutils.tr('Changed Upstream'))
if self.path in untracked:
return (None, '?')
return (None, '')
def run(self):
"""Perform expensive lookups and post corresponding events."""
app = QtGui.QApplication.instance()
entry = GitRepoEntryManager.entry(self.path)
app.postEvent(entry,
GitRepoInfoEvent(Columns.MESSAGE, self.data('message')))
app.postEvent(entry,
GitRepoInfoEvent(Columns.AGE, self.data('date')))
app.postEvent(entry,
GitRepoInfoEvent(Columns.WHO, self.data('author')))
app.postEvent(entry,
GitRepoInfoEvent(Columns.STATUS, self.status()))
TaskRunner.instance().cleanup_task(self)
class GitRepoInfoEvent(QtCore.QEvent):
"""Transport mechanism for communicating from a GitRepoInfoTask."""
def __init__(self, signal, *data):
QtCore.QEvent.__init__(self, QtCore.QEvent.User + 1)
self.signal = signal
self.data = data
def type(self):
return INFO_EVENT_TYPE
class GitRepoItem(QtGui.QStandardItem):
"""
Represents a cell in a treeview.
Many GitRepoItems map to a single repository path.
Each GitRepoItem manages a different cell in the tree view.
One is created for each column -- Name, Status, Age, etc.
"""
def __init__(self, column, path):
QtGui.QStandardItem.__init__(self)
self.setEditable(False)
self.setDragEnabled(False)
entry = GitRepoEntryManager.entry(path)
if column == Columns.STATUS:
QtCore.QObject.connect(entry, SIGNAL(column), self.set_status)
else:
QtCore.QObject.connect(entry, SIGNAL(column), self.setText)
def set_status(self, data):
icon, txt = data
if icon:
self.setIcon(QtGui.QIcon(icon))
else:
self.setIcon(QtGui.QIcon())
self.setText(txt)
class GitRepoNameItem(GitRepoItem):
"""Subclass GitRepoItem to provide a custom type()."""
TYPE = QtGui.QStandardItem.UserType + 1
def __init__(self, path):
GitRepoItem.__init__(self, Columns.NAME, path)
self.path = path
def type(self):
"""
Indicate that this item is of a special user-defined type.
'name' is the only column that registers a user-defined type.
This is done to allow filtering out other columns when determining
which paths are selected.
"""
return GitRepoNameItem.TYPE
+392
View File
@@ -0,0 +1,392 @@
import os
from PyQt4 import QtGui
from PyQt4 import QtCore
from PyQt4.QtCore import Qt
from PyQt4.QtCore import SIGNAL
import cola
from cola import qtutils
from cola import signals
from cola import utils
from cola.views import standard
from cola.widgets import defs
from cola.classic.model import GitRepoNameItem
class RepoDialog(standard.Dialog):
def __init__(self, parent, update=True):
standard.Dialog.__init__(self, parent)
self.setObjectName('classic')
self.tree = RepoTreeView(self)
self.mainlayout = QtGui.QHBoxLayout()
self.setLayout(self.mainlayout)
self.mainlayout.setMargin(0)
self.mainlayout.setSpacing(defs.spacing)
self.mainlayout.addWidget(self.tree)
self.resize(720, 420)
self.connect(self, SIGNAL('updated'), self._updated_callback)
self.model = cola.model()
self.model.add_message_observer(self.model.message_updated,
self._model_updated)
qtutils.add_close_action(self)
if update:
self._model_updated()
# Read-only mode property
mode = property(lambda self: self.model.mode)
def _model_updated(self):
"""Update the title with the current branch and directory name."""
self.emit(SIGNAL('updated'))
def _updated_callback(self):
branch = self.model.currentbranch
curdir = os.getcwd()
msg = 'Repository: %s\nBranch: %s' % (curdir, branch)
self.setToolTip(msg)
title = '%s [%s]' % (self.model.project, branch)
if self.mode in (self.model.mode_diff, self.model.mode_diff_expr):
title += ' *** diff mode***'
elif self.mode == self.model.mode_review:
title += ' *** review mode***'
elif self.mode == self.model.mode_amend:
title += ' *** amending ***'
self.setWindowTitle(title)
class RepoTreeView(QtGui.QTreeView):
"""Provides a filesystem-like view of a git repository."""
def __init__(self, parent):
QtGui.QTreeView.__init__(self, parent)
self.setSortingEnabled(False)
self.setAllColumnsShowFocus(True)
self.setAlternatingRowColors(True)
self.setUniformRowHeights(True)
self.setAnimated(True)
self.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
# Observe model updates
model = cola.model()
model.add_message_observer(model.message_updated,
self.update_actions)
# The non-Qt cola application model
self.connect(self, SIGNAL('expanded(QModelIndex)'), self.size_columns)
self.connect(self, SIGNAL('collapsed(QModelIndex)'), self.size_columns)
self.action_history =\
self._create_action('View History...',
'View history for selected path(s).',
self.view_history,
'Shift+Ctrl+H')
self.action_stage =\
self._create_action('Stage Selected',
'Stage selected path(s) for commit.',
self.stage_selected,
'Ctrl+S')
self.action_unstage =\
self._create_action('Unstage Selected',
'Remove selected path(s) from '
'the staging area.',
self.unstage_selected,
'Ctrl+U')
self.action_difftool =\
self._create_action('View Diff...',
'Launch git-difftool on the current path.',
self.difftool,
'Ctrl+D')
self.action_difftool_predecessor =\
self._create_action('Diff Against Predecessor...',
'Launch git-difftool against previous versions.',
self.difftool_predecessor,
'Shift+Ctrl+D')
self.action_revert =\
self._create_action('Revert Uncommitted Changes...',
'Revert changes to selected path(s).',
self.revert,
'Ctrl+Z')
self.action_editor =\
self._create_action('Launch Editor',
'Edit selected path(s).',
self.editor,
'Ctrl+E')
def size_columns(self):
"""Set the column widths."""
self.resizeColumnToContents(0)
def update_actions(self):
"""Enable/disable actions."""
selection = self.selected_paths()
selected = bool(selection)
staged = bool(self.selected_staged_paths(selection=selection))
modified = bool(self.selected_modified_paths(selection=selection))
unstaged = bool(self.selected_unstaged_paths(selection=selection))
tracked = bool(self.selected_tracked_paths())
self.action_history.setEnabled(selected)
self.action_stage.setEnabled(unstaged)
self.action_unstage.setEnabled(staged)
self.action_difftool.setEnabled(staged or modified)
self.action_difftool_predecessor.setEnabled(tracked)
self.action_revert.setEnabled(tracked)
def contextMenuEvent(self, event):
"""Create a context menu."""
self.update_actions()
menu = QtGui.QMenu(self)
menu.addAction(self.action_editor)
menu.addAction(self.action_stage)
menu.addAction(self.action_unstage)
menu.addSeparator()
menu.addAction(self.action_history)
menu.addAction(self.action_difftool)
menu.addAction(self.action_difftool_predecessor)
menu.addSeparator()
menu.addAction(self.action_revert)
menu.exec_(self.mapToGlobal(event.pos()))
def keyPressEvent(self, event):
"""
Override keyPressEvent to allow LeftArrow to work on non-directories.
When LeftArrow is pressed on a file entry or an unexpanded directory,
then move the current index to the parent directory.
This simplifies navigation using the keyboard.
For power-users, we support Vim keybindings ;-P
"""
# Check whether the item is expanded before calling the base class
# keyPressEvent otherwise we end up collapsing and changing the
# current index in one shot, which we don't want to do.
index = self.currentIndex()
was_expanded = self.isExpanded(index)
was_collapsed = not was_expanded
# Vim keybindings...
# Rewrite the event before marshalling to QTreeView.event()
key = event.key()
# Remap 'H' to 'Left'
if key == QtCore.Qt.Key_H:
event = QtGui.QKeyEvent(event.type(),
QtCore.Qt.Key_Left,
event.modifiers())
# Remap 'J' to 'Down'
elif key == QtCore.Qt.Key_J:
event = QtGui.QKeyEvent(event.type(),
QtCore.Qt.Key_Down,
event.modifiers())
# Remap 'K' to 'Up'
elif key == QtCore.Qt.Key_K:
event = QtGui.QKeyEvent(event.type(),
QtCore.Qt.Key_Up,
event.modifiers())
# Remap 'L' to 'Right'
elif key == QtCore.Qt.Key_L:
event = QtGui.QKeyEvent(event.type(),
QtCore.Qt.Key_Right,
event.modifiers())
# Re-read the event key to take the remappings into account
key = event.key()
# Process the keyPressEvent before changing the current index
# otherwise the event will affect the new index set here
# instead of the original index.
result = QtGui.QTreeView.keyPressEvent(self, event)
# Sync the selection model
self.sync_selection()
# Try to select the first item if the model index is invalid
if not index.isValid():
index = self.model().index(0, 0, QtCore.QModelIndex())
if index.isValid():
self.setCurrentIndex(index)
return result
# Automatically select the first entry when expanding a directory
if (key == QtCore.Qt.Key_Right and was_collapsed and
self.isExpanded(index)):
index = self.moveCursor(self.MoveDown, event.modifiers())
self.setCurrentIndex(index)
# Process non-root entries with valid parents only.
if key == QtCore.Qt.Key_Left and index.parent().isValid():
# File entries have rowCount() == 0
if self.item_from_index(index).rowCount() == 0:
self.setCurrentIndex(index.parent())
# Otherwise, do this for collapsed directories only
elif was_collapsed:
self.setCurrentIndex(index.parent())
return result
def mousePressEvent(self, event):
"""Synchronize the selection on mouse-press."""
result = QtGui.QTreeView.mousePressEvent(self, event)
self.sync_selection()
return result
def sync_selection(self):
"""Push selection into the selection model."""
staged = []
modified = []
unmerged = []
untracked = []
paths = self.selected_paths()
model = cola.model()
model_staged = utils.add_parents(set(model.staged))
model_modified = utils.add_parents(set(model.modified))
model_unmerged = utils.add_parents(set(model.unmerged))
model_untracked =utils.add_parents(set(model.untracked))
for path in paths:
if path in model_unmerged:
unmerged.append(path)
elif path in model_untracked:
untracked.append(path)
elif path in model_staged:
staged.append(path)
elif path in model_modified:
modified.append(path)
else:
staged.append(path)
# Push the new selection into the model.
cola.selection_model().set_selection(staged, modified,
unmerged, untracked)
return paths
def selectionChanged(self, old_selection, new_selection):
"""Override selectionChanged to update available actions."""
result = QtGui.QTreeView.selectionChanged(self, old_selection, new_selection)
self.update_actions()
paths = self.sync_selection()
if paths and self.model().path_is_interesting(paths[0]):
cached = paths[0] in cola.model().staged
cola.notifier().broadcast(signals.diff, paths, cached)
return result
def setModel(self, model):
"""Set the concrete QAbstractItemModel instance."""
QtGui.QTreeView.setModel(self, model)
self.size_columns()
def item_from_index(self, model_index):
"""Return the name item corresponding to the model index."""
index = model_index.sibling(model_index.row(), 0)
return self.model().itemFromIndex(index)
def selected_paths(self):
"""Return the selected paths."""
items = map(self.model().itemFromIndex, self.selectedIndexes())
return [i.path for i in items
if i.type() == GitRepoNameItem.TYPE]
def selected_staged_paths(self, selection=None):
"""Return selected staged paths."""
if not selection:
selection = self.selected_paths()
staged = utils.add_parents(set(cola.model().staged))
return [p for p in selection if p in staged]
def selected_modified_paths(self, selection=None):
"""Return selected modified paths."""
if not selection:
selection = self.selected_paths()
model = cola.model()
modified = utils.add_parents(set(model.modified))
return [p for p in selection if p in modified]
def selected_unstaged_paths(self, selection=None):
"""Return selected unstaged paths."""
if not selection:
selection = self.selected_paths()
model = cola.model()
modified = utils.add_parents(set(model.modified))
untracked = utils.add_parents(set(model.untracked))
unstaged = modified.union(untracked)
return [p for p in selection if p in unstaged]
def selected_tracked_paths(self, selection=None):
"""Return selected tracked paths."""
if not selection:
selection = self.selected_paths()
model = cola.model()
staged = set(self.selected_staged_paths())
modified = set(self.selected_modified_paths())
untracked = utils.add_parents(set(model.untracked))
tracked = staged.union(modified)
return [p for p in selection
if p not in untracked or p in tracked]
def _create_action(self, name, tooltip, slot, shortcut):
"""Create an action with a shortcut, tooltip, and callback slot."""
action = QtGui.QAction(self.tr(name), self)
action.setStatusTip(self.tr(tooltip))
if hasattr(Qt, 'WidgetWithChildrenShortcut'):
action.setShortcutContext(Qt.WidgetWithChildrenShortcut)
action.setShortcut(shortcut)
self.addAction(action)
self.connect(action, SIGNAL('triggered()'), slot)
return action
def view_history(self):
"""Signal that we should view history for paths."""
self.emit(SIGNAL('history(QStringList)'), self.selected_paths())
def stage_selected(self):
"""Signal that we should stage selected paths."""
cola.notifier().broadcast(signals.stage,
self.selected_unstaged_paths())
def unstage_selected(self):
"""Signal that we should stage selected paths."""
cola.notifier().broadcast(signals.unstage,
self.selected_staged_paths())
def difftool(self):
"""Signal that we should launch difftool on a path."""
cola.notifier().broadcast(signals.difftool,
False,
self.selected_tracked_paths())
def difftool_predecessor(self):
"""Diff paths against previous versions."""
paths = self.selected_tracked_paths()
self.emit(SIGNAL('difftool_predecessor'), paths)
def revert(self):
"""Signal that we should revert changes to a path."""
if not qtutils.confirm('Revert Uncommitted Changes?',
'This operation drops uncommitted changes.'
'\nThese changes cannot be recovered.',
'Revert the uncommitted changes?',
'Revert Uncommitted Changes',
default=False,
icon=qtutils.icon('undo.svg')):
return
paths = self.selected_tracked_paths()
cola.notifier().broadcast(signals.checkout,
['HEAD', '--'] + paths)
def editor(self):
"""Signal that we should edit selected paths using an external editor."""
cola.notifier().broadcast(signals.edit, self.selected_paths())
def current_path(self):
"""Return the path for the current item."""
index = self.currentIndex()
if not index.isValid():
return None
return self.item_from_index(index).path
+127
View File
@@ -0,0 +1,127 @@
"""
Maps Qt signals to Command objects.
The command factory connects to the global notifier and
creates commands objects as registered signals are
encountered.
The factory itself is undoable in that it responds to
the undo and redo signals and manages the undo/redo stack.
"""
import cola
from cola import signals
from cola import errors
from cola.decorators import memoize
@memoize
def factory():
"""Return a static instance of the command factory."""
return CommandFactory()
def SLOT(signal, *args, **opts):
"""
Returns a callback that broadcasts a message over the notifier.
"""
def broadcast(*local_args, **opts):
cola.notifier().broadcast(signal, *args, **opts)
return broadcast
class CommandFactory(object):
def __init__(self, context=None):
"""Setup the undo/redo stacks and register for notifications."""
self.undoable = True
self.undostack = []
self.redostack = []
self.signal_to_command = {}
self.callbacks = {}
self.context = context
def has_command(self, signal):
return signal in self.signal_to_command
def add_command(self, signal, command):
"""Register a signal/command pair."""
self.signal_to_command[signal] = command
def add_global_command(self, signal, command):
"""Register a global signal/command pair."""
self.add_command(signal, command)
cola.notifier().connect(signal, self.cmdrunner(signal))
def add_command_wrapper(self, cmd_wrapper):
self.callbacks.update(cmd_wrapper.callbacks)
def prompt_user(self, name, *args, **opts):
try:
return self.callbacks[name](*args, **opts)
except KeyError:
raise NotImplementedError('No callback for "%s' % name)
def clear(self):
"""Clear the undo and redo stacks."""
self.undostack = []
self.redostack = []
def cmdrunner(self, signal):
"""Return a function to create and run a signal's command."""
def run(*args, **opts):
return self.do(signal, *args, **opts)
return run
def do(self, signal, *args, **opts):
"""Given a signal and arguments, run its corresponding command."""
cmdclass = self.signal_to_command[signal]
cmdobj = cmdclass(*args, **opts)
cmdobj.context = self.context
# TODO we disable undo/redo for now; views just need to
# inspect the stack and add menu entries when we enable it.
ok, result = self._do(cmdobj)
if ok and self.undoable and cmdobj.is_undoable():
self.undostack.append(cmdobj)
return result
def _do(self, cmdobj):
try:
result = cmdobj.do()
except errors.UsageError, e:
self.prompt_user(signals.information, e.title, e.message)
return False, None
else:
return True, result
def undo(self):
"""Undo the last command and add it to the redo stack."""
if self.undostack:
cmdobj = self.undostack.pop()
result = cmdobj.undo()
self.redostack.append(cmdobj)
return result
else:
print 'warning: undo stack is empty, doing nothing'
return None
def redo(self):
"""Redo the last command and add it to the undo stack."""
if self.redostack:
cmdobj = self.redostack.pop()
ok, result = self._do(cmdobj)
if ok and cmdobj.is_undoable():
self.undo.append(cmdobj)
else:
self.redostack.push(cmdobj)
return result
else:
print 'warning: redo stack is empty, doing nothing'
def is_undoable(self):
"""Does the undo stack contain any commands?"""
return bool(self.undostack)
def is_redoable(self):
"""Does the redo stack contain any commands?"""
return bool(self.redostack)
+960
View File
@@ -0,0 +1,960 @@
import os
import sys
import platform
from cStringIO import StringIO
import cola
from cola import i18n
from cola import core
from cola import errors
from cola import gitcfg
from cola import gitcmds
from cola import utils
from cola import signals
from cola import cmdfactory
from cola import difftool
from cola.diffparse import DiffParser
from cola.models import selection
_notifier = cola.notifier()
_factory = cmdfactory.factory()
_config = gitcfg.instance()
class BaseCommand(object):
"""Base class for all commands; provides the command pattern"""
def __init__(self):
self.undoable = False
def is_undoable(self):
"""Can this be undone?"""
return self.undoable
def name(self):
"""Return this command's name."""
return self.__class__.__name__
def do(self):
raise NotImplementedError('%s.do() is unimplemented' % self.name())
def undo(self):
raise NotImplementedError('%s.undo() is unimplemented' % self.name())
class Command(BaseCommand):
"""Base class for commands that modify the main model"""
def __init__(self):
"""Initialize the command and stash away values for use in do()"""
# These are commonly used so let's make it easier to write new commands.
BaseCommand.__init__(self)
self.model = cola.model()
self.old_diff_text = self.model.diff_text
self.old_filename = self.model.filename
self.old_mode = self.model.mode
self.old_head = self.model.head
self.new_diff_text = self.old_diff_text
self.new_filename = self.old_filename
self.new_head = self.old_head
self.new_mode = self.old_mode
def do(self):
"""Perform the operation."""
self.model.set_diff_text(self.new_diff_text)
self.model.set_filename(self.new_filename)
self.model.set_head(self.new_head)
self.model.set_mode(self.new_mode)
def undo(self):
"""Undo the operation."""
self.model.set_diff_text(self.old_diff_text)
self.model.set_filename(self.old_filename)
self.model.set_head(self.old_head)
self.model.set_mode(self.old_mode)
class AmendMode(Command):
"""Try to amend a commit."""
def __init__(self, amend):
Command.__init__(self)
self.undoable = True
self.skip = False
self.amending = amend
self.old_commitmsg = self.model.commitmsg
if self.amending:
self.new_mode = self.model.mode_amend
self.new_head = 'HEAD^'
self.new_commitmsg = self.model.prev_commitmsg()
return
# else, amend unchecked, regular commit
self.new_mode = self.model.mode_none
self.new_head = 'HEAD'
self.new_diff_text = ''
self.new_commitmsg = self.model.commitmsg
# If we're going back into new-commit-mode then search the
# undo stack for a previous amend-commit-mode and grab the
# commit message at that point in time.
if not _factory.undostack:
return
undo_count = len(_factory.undostack)
for i in xrange(undo_count):
# Find the latest AmendMode command
idx = undo_count - i - 1
cmdobj = _factory.undostack[idx]
if type(cmdobj) is not AmendMode:
continue
if cmdobj.amending:
self.new_commitmsg = cmdobj.old_commitmsg
break
def do(self):
"""Leave/enter amend mode."""
"""Attempt to enter amend mode. Do not allow this when merging."""
if self.amending:
if os.path.exists(self.model.git.git_path('MERGE_HEAD')):
self.skip = True
_notifier.broadcast(signals.amend, False)
_factory.prompt_user(signals.information,
'Oops! Unmerged',
'You are in the middle of a merge.\n'
'You cannot amend while merging.')
return
self.skip = False
_notifier.broadcast(signals.amend, self.amending)
self.model.set_commitmsg(self.new_commitmsg)
Command.do(self)
self.model.update_file_status()
def undo(self):
if self.skip:
return
self.model.set_commitmsg(self.old_commitmsg)
Command.undo(self)
self.model.update_file_status()
class ApplyDiffSelection(Command):
def __init__(self, staged, selected, offset, selection, apply_to_worktree):
Command.__init__(self)
self.staged = staged
self.selected = selected
self.offset = offset
self.selection = selection
self.apply_to_worktree = apply_to_worktree
def do(self):
if self.model.mode == self.model.mode_branch:
# We're applying changes from a different branch!
parser = DiffParser(self.model,
filename=self.model.filename,
cached=False,
branch=self.model.head)
status, output = \
parser.process_diff_selection(self.selected,
self.offset,
self.selection,
apply_to_worktree=True)
else:
# The normal worktree vs index scenario
parser = DiffParser(self.model,
filename=self.model.filename,
cached=self.staged,
reverse=self.apply_to_worktree)
status, output = \
parser.process_diff_selection(self.selected,
self.offset,
self.selection,
apply_to_worktree=
self.apply_to_worktree)
_notifier.broadcast(signals.log_cmd, status, output)
# Redo the diff to show changes
if self.staged:
diffcmd = DiffStaged([self.model.filename])
else:
diffcmd = Diff([self.model.filename])
diffcmd.do()
self.model.update_file_status()
class ApplyPatches(Command):
def __init__(self, patches):
Command.__init__(self)
patches.sort()
self.patches = patches
def do(self):
diff_text = ''
num_patches = len(self.patches)
orig_head = self.model.git.rev_parse('HEAD')
for idx, patch in enumerate(self.patches):
status, output = self.model.git.am(patch,
with_status=True,
with_stderr=True)
# Log the git-am command
_notifier.broadcast(signals.log_cmd, status, output)
if num_patches > 1:
diff = self.model.git.diff('HEAD^!', stat=True)
diff_text += 'Patch %d/%d - ' % (idx+1, num_patches)
diff_text += '%s:\n%s\n\n' % (os.path.basename(patch), diff)
diff_text += 'Summary:\n'
diff_text += self.model.git.diff(orig_head, stat=True)
# Display a diffstat
self.model.set_diff_text(diff_text)
self.model.update_file_status()
_factory.prompt_user(signals.information,
'Patch(es) Applied',
'%d patch(es) applied:\n\n%s' %
(len(self.patches),
'\n'.join(map(os.path.basename, self.patches))))
class HeadChangeCommand(Command):
"""Changes the model's current head."""
def __init__(self, treeish):
Command.__init__(self)
self.new_head = treeish
self.new_diff_text = ''
def do(self):
Command.do(self)
self.model.update_file_status()
class BranchMode(HeadChangeCommand):
"""Enter into diff-branch mode."""
def __init__(self, treeish, filename):
HeadChangeCommand.__init__(self, treeish)
self.old_filename = self.model.filename
self.new_filename = filename
self.new_mode = self.model.mode_branch
self.new_diff_text = gitcmds.diff_helper(filename=filename,
cached=False,
reverse=True,
branch=treeish)
class Checkout(Command):
"""
A command object for git-checkout.
'argv' is handed off directly to git.
"""
def __init__(self, argv, checkout_branch=False):
Command.__init__(self)
self.argv = argv
self.checkout_branch = checkout_branch
self.new_diff_text = ''
def do(self):
status, output = self.model.git.checkout(with_stderr=True,
with_status=True, *self.argv)
_notifier.broadcast(signals.log_cmd, status, output)
if self.checkout_branch:
self.model.update_status()
else:
self.model.update_file_status()
class CheckoutBranch(Checkout):
"""Checkout a branch."""
def __init__(self, branch, checkout_branch=True):
Checkout.__init__(self, [branch])
class CherryPick(Command):
"""Cherry pick commits into the current branch."""
def __init__(self, commits):
Command.__init__(self)
self.commits = commits
def do(self):
self.model.cherry_pick_list(self.commits)
self.model.update_file_status()
class ResetMode(Command):
"""Reset the mode and clear the model's diff text."""
def __init__(self):
Command.__init__(self)
self.new_mode = self.model.mode_none
self.new_head = 'HEAD'
self.new_diff_text = ''
def do(self):
Command.do(self)
self.model.update_file_status()
class Commit(ResetMode):
"""Attempt to create a new commit."""
def __init__(self, amend, msg):
ResetMode.__init__(self)
self.amend = amend
self.msg = core.encode(msg)
self.old_commitmsg = self.model.commitmsg
self.new_commitmsg = ''
def do(self):
status, output = self.model.commit_with_msg(self.msg, amend=self.amend)
if status == 0:
ResetMode.do(self)
self.model.set_commitmsg(self.new_commitmsg)
title = 'Commit: '
else:
title = 'Commit failed: '
_notifier.broadcast(signals.log_cmd, status, title+output)
class Ignore(Command):
"""Add files to .gitignore"""
def __init__(self, filenames):
Command.__init__(self)
self.filenames = filenames
def do(self):
new_additions = ''
for fname in self.filenames:
new_additions = new_additions + fname + '\n'
for_status = new_additions
if new_additions:
if os.path.exists('.gitignore'):
current_list = utils.slurp('.gitignore')
new_additions = new_additions + current_list
utils.write('.gitignore', new_additions)
_notifier.broadcast(signals.log_cmd,
0,
'Added to .gitignore:\n%s' % for_status)
self.model.update_file_status()
class Delete(Command):
"""Simply delete files."""
def __init__(self, filenames):
Command.__init__(self)
self.filenames = filenames
# We could git-hash-object stuff and provide undo-ability
# as an option. Heh.
def do(self):
rescan = False
for filename in self.filenames:
if filename:
try:
os.remove(filename)
rescan=True
except:
_factory.prompt_user(signals.information,
'Error'
'Deleting "%s" failed.' % filename)
if rescan:
self.model.update_file_status()
class DeleteBranch(Command):
"""Delete a git branch."""
def __init__(self, branch):
Command.__init__(self)
self.branch = branch
def do(self):
status, output = self.model.delete_branch(self.branch)
title = ''
if output.startswith('error:'):
output = 'E' + output[1:]
else:
title = 'Info: '
_notifier.broadcast(signals.log_cmd, status, title + output)
class Diff(Command):
"""Perform a diff and set the model's current text."""
def __init__(self, filenames, cached=False):
Command.__init__(self)
# Guard against the list of files being empty
if not filenames:
return
opts = {}
if cached:
cached = not self.model.read_only()
opts = dict(ref=self.model.head)
self.new_filename = filenames[0]
self.old_filename = self.model.filename
if not self.model.read_only():
if self.model.mode != self.model.mode_amend:
self.new_mode = self.model.mode_worktree
self.new_diff_text = gitcmds.diff_helper(filename=self.new_filename,
cached=cached, **opts)
class DiffMode(HeadChangeCommand):
"""Enter diff mode and clear the model's diff text."""
def __init__(self, treeish):
HeadChangeCommand.__init__(self, treeish)
self.new_mode = self.model.mode_diff
class DiffExprMode(HeadChangeCommand):
"""Enter diff-expr mode and clear the model's diff text."""
def __init__(self, treeish):
HeadChangeCommand.__init__(self, treeish)
self.new_mode = self.model.mode_diff_expr
class Diffstat(Command):
"""Perform a diffstat and set the model's diff text."""
def __init__(self):
Command.__init__(self)
diff = self.model.git.diff(self.model.head,
unified=_config.get('diff.context', 3),
no_color=True,
M=True,
stat=True)
self.new_diff_text = core.decode(diff)
self.new_mode = self.model.mode_worktree
class DiffStaged(Diff):
"""Perform a staged diff on a file."""
def __init__(self, filenames):
Diff.__init__(self, filenames, cached=True)
if not self.model.read_only():
if self.model.mode != self.model.mode_amend:
self.new_mode = self.model.mode_index
class DiffStagedSummary(Command):
def __init__(self):
Command.__init__(self)
cached = not self.model.read_only()
diff = self.model.git.diff(self.model.head,
cached=cached,
no_color=True,
patch_with_stat=True,
M=True)
self.new_diff_text = core.decode(diff)
if not self.model.read_only():
if self.model.mode != self.model.mode_amend:
self.new_mode = self.model.mode_index
class Difftool(Command):
"""Run git-difftool limited by path."""
def __init__(self, staged, filenames):
Command.__init__(self)
self.staged = staged
self.filenames = filenames
def do(self):
if not self.filenames:
return
args = []
if self.staged and not self.model.read_only():
args.append('--cached')
if self.model.head != 'HEAD':
args.append(self.model.head)
args.append('--')
args.extend(self.filenames)
difftool.launch(args)
class Edit(Command):
"""Edit a file using the configured gui.editor."""
def __init__(self, filenames, line_number=None):
Command.__init__(self)
self.filenames = filenames
self.line_number = line_number
def do(self):
filename = self.filenames[0]
if not os.path.exists(filename):
return
editor = self.model.editor()
if 'vi' in editor and self.line_number:
utils.fork([editor, filename, '+'+self.line_number])
else:
utils.fork([editor, filename])
class FormatPatch(Command):
"""Output a patch series given all revisions and a selected subset."""
def __init__(self, to_export, revs):
Command.__init__(self)
self.to_export = to_export
self.revs = revs
def do(self):
status, output = gitcmds.format_patchsets(self.to_export, self.revs)
_notifier.broadcast(signals.log_cmd, status, output)
class GrepMode(Command):
def __init__(self, txt):
"""Perform a git-grep."""
Command.__init__(self)
self.new_mode = self.model.mode_grep
self.new_diff_text = core.decode(self.model.git.grep(txt, n=True))
class LoadCommitMessage(Command):
"""Loads a commit message from a path."""
def __init__(self, path):
Command.__init__(self)
self.undoable = True
self.path = path
self.old_commitmsg = self.model.commitmsg
self.old_directory = self.model.directory
def do(self):
path = self.path
if not path or not os.path.isfile(path):
raise errors.UsageError('Error: cannot find commit template',
'%s: No such file or directory.' % path)
self.model.set_directory(os.path.dirname(path))
self.model.set_commitmsg(utils.slurp(path))
def undo(self):
self.model.set_commitmsg(self.old_commitmsg)
self.model.set_directory(self.old_directory)
class LoadCommitTemplate(LoadCommitMessage):
"""Loads the commit message template specified by commit.template."""
def __init__(self):
LoadCommitMessage.__init__(self, _config.get('commit.template'))
def do(self):
if self.path is None:
raise errors.UsageError('Error: unconfigured commit template',
'A commit template has not been configured.\n'
'Use "git config" to define "commit.template"\n'
'so that it points to a commit template.')
return LoadCommitMessage.do(self)
class LoadPreviousMessage(Command):
"""Try to amend a commit."""
def __init__(self, sha1):
Command.__init__(self)
self.sha1 = sha1
self.old_commitmsg = self.model.commitmsg
self.new_commitmsg = self.model.prev_commitmsg(sha1)
self.undoable = True
def do(self):
self.model.set_commitmsg(self.new_commitmsg)
def undo(self):
self.model.set_commitmsg(self.old_commitmsg)
class Mergetool(Command):
"""Launch git-mergetool on a list of paths."""
def __init__(self, paths):
Command.__init__(self)
self.paths = paths
def do(self):
if not self.paths:
return
utils.fork(['git', 'mergetool', '--no-prompt', '--'] + self.paths)
class OpenRepo(Command):
"""Launches git-cola on a repo."""
def __init__(self, dirname):
Command.__init__(self)
self.new_directory = dirname
def do(self):
self.model.set_directory(self.new_directory)
utils.fork([sys.executable, sys.argv[0], '--repo', self.new_directory])
class Clone(Command):
"""Clones a repository and optionally spawns a new cola session."""
def __init__(self, url, destdir, spawn=True):
Command.__init__(self)
self.url = url
self.new_directory = destdir
self.spawn = spawn
def do(self):
self.model.git.clone(self.url, self.new_directory,
with_stderr=True, with_status=True)
if self.spawn:
utils.fork(['python', sys.argv[0], '--repo', self.new_directory])
rescan = 'rescan'
class Rescan(Command):
"""Rescans for changes."""
def do(self):
self.model.update_status(update_index=True)
class ReviewBranchMode(Command):
"""Enter into review-branch mode."""
def __init__(self, branch):
Command.__init__(self)
self.new_mode = self.model.mode_review
self.new_head = gitcmds.merge_base_parent(branch)
self.new_diff_text = ''
def do(self):
Command.do(self)
self.model.update_status()
class RunConfigAction(Command):
"""Run a user-configured action, typically from the "Tools" menu"""
def __init__(self, name):
Command.__init__(self)
self.name = name
self.model = cola.model()
def do(self):
for env in ('FILENAME', 'REVISION', 'ARGS'):
try:
del os.environ[env]
except KeyError:
pass
rev = None
args = None
opts = _config.get_guitool_opts(self.name)
cmd = opts.get('cmd')
if 'title' not in opts:
opts['title'] = cmd
if 'prompt' not in opts or opts.get('prompt') is True:
prompt = i18n.gettext('Are you sure you want to run %s?') % cmd
opts['prompt'] = prompt
if opts.get('needsfile'):
filename = selection.filename()
if not filename:
_factory.prompt_user(signals.information,
'Please select a file',
'"%s" requires a selected file' % cmd)
return
os.environ['FILENAME'] = filename
if opts.get('revprompt') or opts.get('argprompt'):
while True:
ok = _factory.prompt_user(signals.run_config_action, cmd, opts)
if not ok:
return
rev = opts.get('revision')
args = opts.get('args')
if opts.get('revprompt') and not rev:
title = 'Invalid Revision'
msg = 'The revision expression cannot be empty.'
_factory.prompt_user(signals.critical, title, msg)
continue
break
elif opts.get('confirm'):
title = os.path.expandvars(opts.get('title'))
prompt = os.path.expandvars(opts.get('prompt'))
if not _factory.prompt_user(signals.question, title, prompt):
return
if rev:
os.environ['REVISION'] = rev
if args:
os.environ['ARGS'] = args
title = os.path.expandvars(cmd)
_notifier.broadcast(signals.log_cmd, 0, 'running: ' + title)
cmd = ['sh', '-c', cmd]
if opts.get('noconsole'):
status, out, err = utils.run_command(cmd, flag_error=False)
else:
status, out, err = _factory.prompt_user(signals.run_command,
title, cmd)
_notifier.broadcast(signals.log_cmd, status,
'stdout: %s\nstatus: %s\nstderr: %s' %
(out.rstrip(), status, err.rstrip()))
if not opts.get('norescan'):
self.model.update_status()
return status
class SetDiffText(Command):
def __init__(self, text):
Command.__init__(self)
self.undoable = True
self.new_diff_text = text
class ShowUntracked(Command):
"""Show an untracked file."""
# We don't actually do anything other than set the mode right now.
# TODO check the mimetype for the file and handle things
# generically.
def __init__(self, filenames):
Command.__init__(self)
self.new_mode = self.model.mode_worktree
# TODO new_diff_text = utils.file_preview(filenames[0])
class SignOff(Command):
def __init__(self):
Command.__init__(self)
self.undoable = True
self.old_commitmsg = self.model.commitmsg
def do(self):
signoff = self.signoff()
if signoff in self.model.commitmsg:
return
self.model.set_commitmsg(self.model.commitmsg + '\n' + signoff)
def undo(self):
self.model.set_commitmsg(self.old_commitmsg)
def signoff(self):
try:
import pwd
user = pwd.getpwuid(os.getuid()).pw_name
except ImportError:
user = os.getenv('USER', 'unknown')
name = _config.get('user.name', user)
email = _config.get('user.email', '%s@%s' % (user, platform.node()))
return '\nSigned-off-by: %s <%s>' % (name, email)
class Stage(Command):
"""Stage a set of paths."""
def __init__(self, paths):
Command.__init__(self)
self.paths = paths
def do(self):
msg = 'Staging: %s' % (', '.join(self.paths))
_notifier.broadcast(signals.log_cmd, 0, msg)
self.model.stage_paths(self.paths)
class StageModified(Stage):
"""Stage all modified files."""
def __init__(self):
Stage.__init__(self, None)
self.paths = self.model.modified
class StageUntracked(Stage):
"""Stage all untracked files."""
def __init__(self):
Stage.__init__(self, None)
self.paths = self.model.untracked
class Tag(Command):
"""Create a tag object."""
def __init__(self, name, revision, sign=False, message=''):
Command.__init__(self)
self._name = name
self._message = core.encode(message)
self._revision = revision
self._sign = sign
def do(self):
log_msg = 'Tagging: "%s" as "%s"' % (self._revision, self._name)
opts = {}
if self._message:
opts['F'] = self.model.tmp_filename()
utils.write(opts['F'], self._message)
if self._sign:
log_msg += ', GPG-signed'
opts['s'] = True
status, output = self.model.git.tag(self._name,
self._revision,
with_status=True,
with_stderr=True,
**opts)
else:
opts['a'] = bool(self._message)
status, output = self.model.git.tag(self._name,
self._revision,
with_status=True,
with_stderr=True,
**opts)
if 'F' in opts:
os.unlink(opts['F'])
if output:
log_msg += '\nOutput:\n%s' % output
_notifier.broadcast(signals.log_cmd, status, log_msg)
if status == 0:
self.model.update_status()
class Unstage(Command):
"""Unstage a set of paths."""
def __init__(self, paths):
Command.__init__(self)
self.paths = paths
def do(self):
msg = 'Unstaging: %s' % (', '.join(self.paths))
_notifier.broadcast(signals.log_cmd, 0, msg)
self.model.unstage_paths(self.paths)
class UnstageAll(Command):
"""Unstage all files; resets the index."""
def do(self):
self.model.unstage_all()
class UnstageSelected(Unstage):
"""Unstage selected files."""
def __init__(self):
Unstage.__init__(self, cola.selection_model().staged)
class UntrackedSummary(Command):
"""List possible .gitignore rules as the diff text."""
def __init__(self):
Command.__init__(self)
untracked = self.model.untracked
suffix = len(untracked) > 1 and 's' or ''
io = StringIO()
io.write('# %s untracked file%s\n' % (len(untracked), suffix))
if untracked:
io.write('# possible .gitignore rule%s:\n' % suffix)
for u in untracked:
io.write('/'+core.encode(u))
self.new_diff_text = core.decode(io.getvalue())
class UpdateStatus(Command):
"""Update the status of a list of files."""
def __init__(self, files):
Command.__init__(self)
self.files = files
def do(self):
self.model.update_status_of_files(self.files)
class VisualizeAll(Command):
"""Visualize all branches."""
def do(self):
browser = self.model.history_browser()
utils.fork([browser, '--all'])
class VisualizeCurrent(Command):
"""Visualize all branches."""
def do(self):
browser = self.model.history_browser()
utils.fork([browser, self.model.currentbranch])
class VisualizePaths(Command):
"""Path-limited visualization."""
def __init__(self, paths):
Command.__init__(self)
browser = self.model.history_browser()
if paths:
self.argv = [browser] + paths
else:
self.argv = [browser]
def do(self):
utils.fork(self.argv)
visualize_revision = 'visualize_revision'
class VisualizeRevision(Command):
"""Visualize a specific revision."""
def __init__(self, revision, paths=None):
Command.__init__(self)
self.revision = revision
self.paths = paths
def do(self):
argv = [self.model.history_browser()]
if self.revision:
argv.append(self.revision)
if self.paths:
argv.append('--')
argv.extend(self.paths)
utils.fork(argv)
def register():
"""
Register signal mappings with the factory.
These commands are automatically created and run when
their corresponding signal is broadcast by the notifier.
"""
signal_to_command_map = {
signals.amend_mode: AmendMode,
signals.apply_diff_selection: ApplyDiffSelection,
signals.apply_patches: ApplyPatches,
signals.branch_mode: BranchMode,
signals.clone: Clone,
signals.checkout: Checkout,
signals.checkout_branch: CheckoutBranch,
signals.cherry_pick: CherryPick,
signals.commit: Commit,
signals.delete: Delete,
signals.delete_branch: DeleteBranch,
signals.diff: Diff,
signals.diff_mode: DiffMode,
signals.diff_expr_mode: DiffExprMode,
signals.diff_staged: DiffStaged,
signals.diffstat: Diffstat,
signals.difftool: Difftool,
signals.edit: Edit,
signals.format_patch: FormatPatch,
signals.grep: GrepMode,
signals.ignore: Ignore,
signals.load_commit_message: LoadCommitMessage,
signals.load_commit_template: LoadCommitTemplate,
signals.load_previous_message: LoadPreviousMessage,
signals.modified_summary: Diffstat,
signals.mergetool: Mergetool,
signals.open_repo: OpenRepo,
signals.rescan: Rescan,
signals.reset_mode: ResetMode,
signals.review_branch_mode: ReviewBranchMode,
signals.run_config_action: RunConfigAction,
signals.set_diff_text: SetDiffText,
signals.show_untracked: ShowUntracked,
signals.signoff: SignOff,
signals.stage: Stage,
signals.stage_modified: StageModified,
signals.stage_untracked: StageUntracked,
signals.staged_summary: DiffStagedSummary,
signals.tag: Tag,
signals.unstage: Unstage,
signals.unstage_all: UnstageAll,
signals.unstage_selected: UnstageSelected,
signals.untracked_summary: UntrackedSummary,
signals.update_status: UpdateStatus,
signals.visualize_all: VisualizeAll,
signals.visualize_current: VisualizeCurrent,
signals.visualize_paths: VisualizePaths,
signals.visualize_revision: VisualizeRevision,
}
for signal, cmd in signal_to_command_map.iteritems():
_factory.add_global_command(signal, cmd)
+14
View File
@@ -0,0 +1,14 @@
try:
set = set
except NameError:
from sets import Set as set
set = set
try:
import hashlib
except ImportError:
import md5
class hashlib(object):
@staticmethod
def new(*args):
return md5.new()
View File
+364
View File
@@ -0,0 +1,364 @@
"""This controller handles the compare commits dialog."""
from cola import qtutils
from cola import difftool
from cola import gitcmds
from cola.qobserver import QObserver
from cola.models.compare import CompareModel
from cola.models.compare import BranchCompareModel
from cola.views.compare import BranchCompareView
from cola.views.compare import CompareView
from cola.widgets.browse import BrowseDialog
def compare_file():
"""Launches a dialog for comparing revisions touching a file path"""
filename = BrowseDialog.select_file('HEAD')
if not filename:
return
compare(filename)
def compare(filename=None):
"""Launches a dialog for comparing a pair of commits"""
model = CompareModel()
view = CompareView(qtutils.active_window())
ctl = CompareController(model, view, filename)
view.show()
return ctl
def branch_compare():
"""Launches a dialog for comparing a pair of branches"""
model = BranchCompareModel()
view = BranchCompareView(qtutils.active_window())
ctl = BranchCompareController(model, view)
view.show()
return ctl
class BranchCompareController(QObserver):
"""Provides a dialog for comparing local and remote git branches"""
BRANCH_POINT = '*** Branch Point ***'
SANDBOX = '*** Sandbox ***'
LOCAL = 'Local'
def __init__(self, model, view):
QObserver.__init__(self, model, view)
self.add_observables('left_combo', 'right_combo',
'left_list', 'right_list',
'diff_files')
# wire the compare button to the double-click action and
# the combo boxes to the "update list of changed files" action
self.add_callbacks(button_compare =
self.diff_files_doubleclick,
left_combo =
lambda x: self.update_combo_boxes(left=True),
right_combo =
lambda x: self.update_combo_boxes(left=False),
left_list =
self.update_diff_files,
right_list =
self.update_diff_files)
# aka notify observers
self.refresh_view()
# Pre-select the 0th elements
self.view.right_combo.setCurrentIndex(1)
item = self.view.left_list.item(0)
self.view.left_list.setCurrentItem(item)
self.view.left_list.setItemSelected(item, True)
item = self.view.right_list.item(0)
self.view.right_list.setCurrentItem(item)
self.view.right_list.setItemSelected(item, True)
def update_diff_files(self, *rest):
"""Updates the list of files whenever the selection changes"""
if (not self.model.has_param('left_list_item') or
not self.model.has_param('right_list_item')):
return
# Left and Right refer to the comparison pair (l,r)
left_item = self.model.left_list_item
right_item = self.model.right_list_item
if (not left_item or not right_item or
left_item == right_item):
self.model.set_diff_files([])
return
left_item = self.remote_ref(left_item)
right_item = self.remote_ref(right_item)
# If any of the selection includes sandbox then we
# generate the same diff, regardless. This means we don't
# support reverse diffs against sandbox aka worktree.
if (left_item == BranchCompareController.SANDBOX or
right_item == BranchCompareController.SANDBOX):
self.use_sandbox = True
if left_item == BranchCompareController.SANDBOX:
self.diff_arg = right_item
else:
self.diff_arg = left_item
else:
self.diff_arg = '%s..%s' % (left_item, right_item)
self.use_sandbox = False
# start and end as in 'git diff start end'
self.start = left_item
self.end = right_item
# TODO leverage Qt's model/view architecture
files = gitcmds.diff_filenames(self.diff_arg)
self.model.set_diff_files(files)
icon = qtutils.icon('script.png')
for idx in xrange(0, self.view.diff_files.topLevelItemCount()):
item = self.view.diff_files.topLevelItem(idx)
item.setIcon(0, icon)
def remote_ref(self, branch):
"""Returns the remote ref for 'git diff [local] [remote]'
"""
if branch == BranchCompareController.BRANCH_POINT:
# Compare against the branch point so find the merge-base
branch = gitcmds.current_branch()
tracked_branch = gitcmds.tracked_branch()
if tracked_branch:
return self.model.git.merge_base(branch, tracked_branch)
else:
remote_branches = gitcmds.branch_list(remote=True)
remote_branch = 'origin/%s' % branch
if remote_branch in remote_branches:
return self.model.git.merge_base(branch, remote_branch)
elif 'origin/master' in remote_branches:
return self.model.git.merge_base(branch, 'origin/master')
else:
return 'HEAD'
else:
# Compare against the remote branch
return branch
def update_combo_boxes(self, left=False):
"""Update listwidgets from the combobox selection
Returns a closure to update either the left or right listwidgets
to reflect the available items.
"""
if left:
which = self.model.left_combo_item
param = 'left_list'
else:
which = self.model.right_combo_item
param = 'right_list'
if not which:
return
# If we're looking at "local" stuff then provide the
# sandbox as a valid choice. If we're looking at
# "remote" stuff then also include the branch point.
if which == self.LOCAL:
new_list = ([BranchCompareController.SANDBOX]+
self.model.local_branches)
else:
new_list = ([BranchCompareController.BRANCH_POINT] +
self.model.remote_branches)
# Update the list widget
self.model.notification_enabled = True
self.model.set_param(param, new_list)
def diff_files_doubleclick(self):
"""Shows the diff for a specific file
"""
tree_widget = self.view.diff_files
id_num, selected = qtutils.selected_treeitem(tree_widget)
if not selected:
qtutils.information('Oops!', 'Please select a file to compare')
return
filename = self.model.diff_files[id_num]
self._compare_file(filename)
def _compare_file(self, filename):
"""Initiates the difftool session"""
if self.use_sandbox:
arg = self.diff_arg
else:
arg = '%s..%s' % (self.start, self.end)
difftool.launch([arg, '--', filename])
class CompareController(QObserver):
"""Drives the "Commit->Compare..." dialog.
"""
def __init__(self, model, view, filename=None):
QObserver.__init__(self, model, view)
self.filename = filename
if self.filename:
# If we've specified a filename up front then there's
# no need to show the list of "changed files" since
# we're only interested in the specified file.
self.view.compare_files.hide()
self.add_observables('descriptions_start', 'descriptions_end',
'revision_start', 'revision_end',
'compare_files', 'num_results',
'show_versions')
# The spinbox and checkbox should both call the generic update method
self.add_actions(num_results = self.update_results)
self.add_actions(show_versions = self.update_results)
# Clicking on a listwidget item should update the list of
# "changed files". Clicking on the "Compare" button should
# initiate a difftool session.
self.add_callbacks(descriptions_start =
lambda *x: self.update_widgets(left=True),
descriptions_end =
lambda *x: self.update_widgets(left=False),
button_compare =
self.compare_selected_file)
self.refresh_view()
# if we can, preselect the latest commit
revisions = self.update_results()
last = len(revisions)
if (last > 1
and self.view.descriptions_start.topLevelItemCount() > last-1
and self.view.descriptions_end .topLevelItemCount() > last-1):
# select the 2nd item on the left treewidget
startitem = self.view.descriptions_start.topLevelItem(last-2)
self.view.descriptions_start.setCurrentItem(startitem)
self.view.descriptions_start.setItemSelected(startitem, True)
# select the 1st item on the right treewidget
enditem = self.view.descriptions_end.topLevelItem(last-1)
self.view.descriptions_end.setCurrentItem(enditem)
self.view.descriptions_end.setItemSelected(enditem, True)
def distance_from_end(self, tree_widget):
"""Returns a (selected, end-index) tuple based on the selection
"""
item_id, item_selected = qtutils.selected_treeitem(tree_widget)
if item_selected:
item_count = tree_widget.topLevelItemCount()
item_delta = item_count - item_id
return (item_selected, item_delta)
else:
return (item_selected, 0)
def select_nth_item_from_end(self, tree_widget, delta):
"""Selects an item relative to the end of the treeitem list
We select from the end to properly handle changes in the number of
displayed commits.
"""
count = self.view.descriptions_start.topLevelItemCount()
idx = count - delta
qtutils.set_selected_item(tree_widget, idx)
def update_results(self, *args):
"""Updates the "changed files" list whenever selection changes
"""
# We use "distance from end" since the number of entries can change
tree_widget = self.view.descriptions_start
start_selected, start_delta = self.distance_from_end(tree_widget)
tree_widget = self.view.descriptions_end
end_selected, end_delta = self.distance_from_end(tree_widget)
# Notificaiton is disabled when inside action callbacks
# TODO use setBlockSignals(True) on widgets instead?
self.model.notification_enabled = True
show_versions = self.model.show_versions
revs = self.model.update_revision_lists(filename=self.filename,
show_versions=show_versions)
# Restore the previous selection
if start_selected:
tree_widget = self.view.descriptions_start
self.select_nth_item_from_end(tree_widget, start_delta)
if end_selected:
tree_widget = self.view.descriptions_end
self.select_nth_item_from_end(tree_widget, end_delta)
return revs
def update_widgets(self, left=True):
"""Updates the list of available revisions for comparison
"""
# This callback can be triggered by either the 'start'
# listwidget or the 'end' list widget. The behavior
# is identical; the only difference is the attribute names.
if left:
tree_widget = self.view.descriptions_start
revisions_param = 'revisions_start'
revision_param = 'revision_start'
else:
tree_widget = self.view.descriptions_end
revisions_param = 'revisions_end'
revision_param = 'revision_end'
# Is anything selected?
id_num, selected = qtutils.selected_treeitem(tree_widget)
if not selected:
return
# Is this a valid revision?
revisionlist = self.model.param(revisions_param)
if id_num < len(revisionlist):
revision = self.model.param(revisions_param)[id_num]
self.model.set_param(revision_param, revision)
# get the changed files list
start = self.model.revision_start
end = self.model.revision_end
files = gitcmds.changed_files(start, end)
# get the old name of any renamed files, and prune them
# from the changes list
renamed_files = gitcmds.renamed_files(start, end)
for renamed in renamed_files:
try:
files.remove(renamed)
except:
pass
# Sets the "changed files" list
self.model.set_compare_files(files)
# Updates the listwidget's icons
icon = qtutils.icon('script.png')
for idx in xrange(0, self.view.compare_files.topLevelItemCount()):
item = self.view.compare_files.topLevelItem(idx)
item.setIcon(0, icon)
# Throw the selected SHA-1 into the clipboard
qtutils.set_clipboard(self.model.param(revision_param))
def compare_selected_file(self):
"""Compares the currently selected file
"""
# When a filename was provided in the constructor then we
# simply compare that file
if self.filename:
self._compare_file(self.filename)
return
# Otherwise, use the selection to choose the compared file
tree_widget = self.view.compare_files
id_num, selected = qtutils.selected_treeitem(tree_widget)
if not selected:
qtutils.information('Oops!', 'Please select a file to compare')
return
filename = self.model.compare_files[id_num]
self._compare_file(filename)
def compare_files_doubleclick(self, tree_item, column):
"""Compares a file when it is double-clicked
"""
# Assumes the listwidget's indexes matches the model's index
idx = self.view.compare_files.indexOfTopLevelItem(tree_item)
filename = self.model.compare_files[idx]
self._compare_file(filename)
def _compare_file(self, filename):
"""Initiates a difftool session for a single file
"""
arg = '%s..%s' % (self.model.revision_start,
self.model.revision_end)
difftool.launch([arg, '--', filename])
+150
View File
@@ -0,0 +1,150 @@
"""This controller handles the create branch dialog."""
from cola import gitcmds
from cola import utils
from cola import qtutils
from cola.views import createbranch
from cola.qobserver import QObserver
from cola.main.model import MainModel
def create_new_branch(revision=''):
"""Launches a dialog for creating a new branch"""
model = MainModel()
model.update_status()
model.revision = revision
model.local_branch = ''
view = createbranch.CreateBranchView(qtutils.active_window())
ctl = CreateBranchController(model, view)
model.set_revision(revision)
view.show()
return ctl
class CreateBranchController(QObserver):
"""Provides control for the "create branch" dialog"""
def __init__(self, model, view):
QObserver.__init__(self, model, view)
self._remoteclicked = False
self.add_observables('revision', 'local_branch')
self.add_callbacks(branch_list = self.branch_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):
"""Creates a branch; called by the "Create Branch" button"""
revision = self.model.revision
branch = self.model.local_branch
existing_branches = self.model.local_branches
if not branch or not revision:
qtutils.critical('Missing Data',
'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.critical('Branch Exists', msg)
return
# Whether we should prompt the user for lost commits
commits = gitcmds.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.')),
])
if not qtutils.confirm('Reset Branch?',
'\n'.join(lines),
'Reset "%s" to "%s"?' % (branch, revision),
'Reset Branch',
default=False,
icon=qtutils.icon('undo.svg')):
return
# TODO handle fetch
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()
chkout = self.view.checkout_checkbox.isChecked()
status, output = self.model.create_branch(branch, revision, track=track)
qtutils.log(status, output)
if status == 0 and chkout:
status, output = self.model.git.checkout(branch,
with_status=True,
with_stderr=True)
qtutils.log(status, output)
self.view.accept()
def branch_item_changed(self, *rest):
"""This callback is called when the branch selection changes"""
# When the branch selection changes then we should update
# the "Revision Expression" accordingly.
qlist = self.view.branch_list
(row, selected) = qtutils.selected_row(qlist)
if not selected:
return
sources = self._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
# Signal that we've clicked on a remote branch
self._remoteclicked = True
self.model.set_local_branch(branch)
self._remoteclicked = False
def display_model(self):
"""Sets the branch list to the available branches
"""
branches = self._branch_sources()
qtutils.set_items(self.view.branch_list, branches)
def _branch_sources(self):
"""Get the list of items for populating the branch root list.
"""
if self.view.local_radio.isChecked():
return self.model.local_branches
elif self.view.remote_radio.isChecked():
return self.model.remote_branches
elif self.view.tag_radio.isChecked():
return self.model.tags
+53
View File
@@ -0,0 +1,53 @@
"""Provides CreateTagController to interact with the CreateTag view."""
import cola
from cola import signals
from cola import qobserver
from cola import qtutils
from cola.models import tag
from cola.views import createtag
def create_tag(revision=''):
"""Entry point for external callers."""
model = tag.TagModel()
if revision:
model.revision = [revision]
view = createtag.CreateTag(qtutils.active_window())
ctl = CreateTagController(model, view)
view.show()
return ctl
class CreateTagController(qobserver.QObserver):
def __init__(self, model, view):
qobserver.QObserver.__init__(self, model, view)
self.add_observables('tag_name', 'tag_msg', 'revision', 'sign_tag')
self.add_callbacks(create_button=self.create_tag)
self.refresh_view()
def create_tag(self):
"""Verifies inputs and emits a notifier tag message."""
if not self.model.tag_name:
cola.notifier().broadcast(signals.critical,
'Missing Name',
'You must name the tag.')
return
if (self.model.sign_tag and
not self.model.tag_msg and
not qtutils.confirm('Missing Tag Message',
'Tag-signing was requested but the tag '
'message is empty.',
'An unsigned, lightweight tag will be '
'created instead.\n'
'Create an unsigned tag?',
'Create Unsigned Tag',
default=False,
icon=qtutils.save_icon())):
return
cola.notifier().broadcast(signals.tag,
self.model.tag_name,
self.model.revision_item,
sign=self.model.sign_tag,
message=self.model.tag_msg)
self.view.accept()
+287
View File
@@ -0,0 +1,287 @@
"""This controller handles the remote dialog."""
import fnmatch
from PyQt4 import QtGui
from PyQt4 import QtCore
from PyQt4.QtCore import Qt
from PyQt4.QtCore import SIGNAL
import cola
from cola import gitcmds
from cola import utils
from cola import qtutils
from cola.views import remote
from cola.qobserver import QObserver
from cola.main.model import MainModel
def remote_action(action):
"""Launches fetch/push/pull dialogs."""
# TODO: subclass model
model = MainModel()
global_model = cola.model()
model.currentbranch = global_model.currentbranch
model.local_branches = global_model.local_branches
model.remote_branches = global_model.remote_branches
model.tags = global_model.tags
model.remotes = global_model.remotes
model.local_branch = ''
model.remote_branch = ''
model.remotename = ''
model.tags_checkbox = False
model.rebase_checkbox = False
model.ffwd_only_checkbox = True
view = remote.RemoteView(qtutils.active_window(), action)
ctl = RemoteController(model, view, action)
view.show()
return ctl
class RemoteController(QObserver):
"""Provides control for the remote-action dialog."""
def __init__(self, model, view, action):
QObserver.__init__(self, model, view)
self.add_observables('remotename',
'remotes',
'local_branch',
'local_branches',
'remote_branch',
'remote_branches',
'tags_checkbox',
'rebase_checkbox',
'ffwd_only_checkbox')
self.action = action
"""The current mode; one of fetch/push/pull"""
self.action_method = {
'fetch': self.gen_remote_callback(self.model.fetch_helper),
'push': self.gen_remote_callback(self.model.push_helper),
'pull': self.gen_remote_callback(self.model.pull_helper),
} [action]
self._tasks = []
self.progress = QtGui.QProgressDialog(self.view)
self.progress.setRange(0, 0)
self.progress.setCancelButton(None)
self.progress.setWindowTitle('git ' + action)
self.progress.setWindowModality(Qt.WindowModal)
"""Callbacks corresponding to the 3 (fetch/push/pull) modes"""
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.view.connect(self.view, SIGNAL('action_completed'),
self._action_completed)
self.refresh_view()
remotes = self.model.remotes
if 'origin' in remotes:
idx = remotes.index('origin')
if self.view.select_remote(idx):
self.model.set_remotename('origin')
else:
if self.view.select_first_remote():
self.model.set_remotename(remotes[0])
# Trim the remote list to just the default remote
self.update_remotes()
# Default to "git fetch origin master"
if action == 'fetch' or action == 'pull':
self.model.set_local_branch('')
self.model.set_remote_branch('')
return
# Select the current branch by default for push
if action == 'push':
branch = self.model.currentbranch
try:
idx = self.model.local_branches.index(branch)
except ValueError:
return
if self.view.select_local_branch(idx):
self.model.set_local_branch(branch)
self.model.set_remote_branch('')
def display_remotes(self, widget):
"""Display the available remotes in a listwidget"""
displayed = []
for remotename in self.model.remotes:
url = self.model.remote_url(remotename, self.action)
display = ('%s\t(%s %s)'
% (remotename, unicode(self.tr('URL:')), url))
displayed.append(display)
qtutils.set_items(widget,displayed)
def update_remotes(self,*rest):
"""Update the remote name when a remote from the list is selected"""
widget = self.view.remotes
remotes = self.model.remotes
selection = qtutils.selected_item(widget, remotes)
if not selection:
return
self.model.set_remotename(selection)
self.view.remotename.selectAll()
if self.action != 'pull':
pass
all_branches = gitcmds.branch_list(remote=True)
branches = []
pat = selection + '/*'
for branch in all_branches:
if fnmatch.fnmatch(branch, pat):
branches.append(branch)
if branches:
self.model.set_remote_branches(branches)
else:
self.model.set_remote_branches(all_branches)
self.model.set_remote_branch('')
def update_local_branches(self,*rest):
"""Update the local/remote branch names when a branch is selected"""
branches = self.model.local_branches
widget = self.view.local_branches
selection = qtutils.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):
"""Update the remote branch name when a branch is selected"""
widget = self.view.remote_branches
branches = self.model.remote_branches
selection = qtutils.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 common_args(self):
"""Returns git arguments common to fetch/push/pulll"""
# TODO move to model
return (self.model.remotename,
{
'local_branch': self.model.local_branch,
'remote_branch': self.model.remote_branch,
'ffwd': self.model.ffwd_only_checkbox,
'tags': self.model.tags_checkbox,
'rebase': self.model.rebase_checkbox,
})
#+-------------------------------------------------------------
#+ Actions
def gen_remote_callback(self, modelaction):
"""Generates a Qt callback for fetch/push/pull.
"""
def remote_callback():
if not self.model.remotename:
errmsg = self.tr('No repository selected.')
qtutils.log(1, errmsg)
return
remote, kwargs = self.common_args()
action = self.action
# Check if we're about to create a new branch and warn.
if action == 'push' and not self.model.remote_branch:
branch = self.model.local_branch
candidate = '%s/%s' % (remote, branch)
if candidate not in self.model.remote_branches:
title = 'Push'
msg = 'Branch "%s" does not exist in %s.' % (branch, remote)
msg += '\nA new remote branch will be published.'
info_txt= 'Create a new remote branch?'
ok_text = 'Create Remote Branch'
if not qtutils.confirm(title, msg, info_txt, ok_text,
default=False,
icon=qtutils.git_icon()):
return
if not self.model.ffwd_only_checkbox:
title = 'Force %s?' % action.title()
ok_text = 'Force %s' % action.title()
if action == 'fetch':
msg = 'Non-fast-forward fetch overwrites local history!'
info_txt = 'Force fetching from %s?' % remote
elif action == 'push':
msg = ('Non-fast-forward push overwrites published '
'history!\n(Did you pull first?)')
info_txt = 'Force push to %s?' % remote
else: # pull: shouldn't happen since the controls are hidden
msg = "You probably don't want to do this.\n\tContinue?"
return
if not qtutils.confirm(title, msg, info_txt, ok_text,
default=False,
icon=qtutils.discard_icon()):
return
# Disable the GUI by default
self.view.setEnabled(False)
self.progress.setEnabled(True)
QtGui.QApplication.setOverrideCursor(Qt.WaitCursor)
# Show a nice progress bar
self.progress.setLabelText('Updating...')
self.progress.show()
# Use a thread to update in the background
task = ActionTask(self.view, modelaction, remote, kwargs)
self._tasks.append(task)
QtCore.QThreadPool.globalInstance().start(task)
return remote_callback
def _action_completed(self, task, status, output):
# Grab the results of the action and finish up
if task in self._tasks:
self._tasks.remove(task)
if not output: # git fetch --tags --verbose doesn't print anything...
output = self.tr('Already up-to-date.')
# Force the status to 1 so that we always display the log
qtutils.log(1, output)
self.progress.close()
QtGui.QApplication.restoreOverrideCursor()
if status != 0 and self.action == 'push':
message = 'Error pushing to "%s".\n\nPull first?' % self.model.remotename
qtutils.critical('Push Error',
message=message, details=output)
else:
title = self.view.windowTitle()
if status == 0:
result = 'succeeded'
else:
result = 'returned exit status %d' % status
message = '"git %s" %s' % (self.action, result)
qtutils.information(title,
message=message, details=output)
self.view.accept()
class ActionTask(QtCore.QRunnable):
def __init__(self, sender, modelaction, remote, kwargs):
QtCore.QRunnable.__init__(self)
self._sender = sender
self._modelaction = modelaction
self._remote = remote
self._kwargs = kwargs
def run(self):
"""Runs the model action and captures the result"""
status, output = self._modelaction(self._remote, **self._kwargs)
self._sender.emit(SIGNAL('action_completed'), self, status, output)
+251
View File
@@ -0,0 +1,251 @@
"""This controller handles the search dialog."""
import os
import re
import time
import shlex
import subprocess
from PyQt4 import QtGui
import cola
from cola import gitcmds
from cola import qtutils
from cola.qobserver import QObserver
from cola.models.search import SearchModel
from cola.views.search import SearchView
# 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'
def search():
"""Return a callback to handle various search actions."""
search_commits(cola.model(), qtutils.active_window())
class SearchEngine(object):
def __init__(self, model):
self.model = model
def rev_args(self):
max_count = self.model.max_results
return {
'no_color': True,
'max-count': max_count,
'pretty': 'format:%H %aN - %s - %ar',
}
def common_args(self):
return (self.model.query, self.rev_args())
def search(self):
if not self.validate():
return
return self.results()
def validate(self):
return len(self.model.query) > 1
def revisions(self, *args, **kwargs):
revlist = self.model.git.log(*args, **kwargs)
return gitcmds.parse_rev_list(revlist)
def results(self):
pass
class RevisionSearch(SearchEngine):
def results(self):
query, args = self.common_args()
expr = re.compile(query)
revs = self.revisions(all=True, **args)
return [ r for r in revs if expr.match(r[0]) ]
class RevisionRangeSearch(SearchEngine):
def __init__(self, model):
SearchEngine.__init__(self, model)
self.RE = re.compile(r'[^.]*\.\..*')
def validate(self):
return bool(self.RE.match(self.model.query))
def results(self):
query, kwargs = self.common_args()
return self.revisions(query, **kwargs)
class PathSearch(SearchEngine):
def results(self):
query, args = self.common_args()
try:
paths = ['--'] + shlex.split(str(query))
except UnicodeEncodeError:
paths = ['--'] + query.split(' ')
return self.revisions(all=True, *paths, **args)
class MessageSearch(SearchEngine):
def results(self):
query, kwargs = self.common_args()
return self.revisions(all=True, grep=query, **kwargs)
class AuthorSearch(SearchEngine):
def results(self):
query, kwargs = self.common_args()
return self.revisions(all=True, author=query, **kwargs)
class CommitterSearch(SearchEngine):
def results(self):
query, kwargs = self.common_args()
return self.revisions(all=True, committer=query, **kwargs)
class DiffSearch(SearchEngine):
def results(self):
query, kwargs = self.common_args()
return gitcmds.parse_rev_list(
self.model.git.log('-S'+query, all=True, **kwargs))
class DateRangeSearch(SearchEngine):
def validate(self):
return True
def results(self):
kwargs = self.rev_args()
start_date = self.model.start_date
end_date = self.model.end_date
return self.revisions(date='iso',
all=True,
after=start_date,
before=end_date,
**kwargs)
# 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):
QObserver.__init__(self, model, view)
self.add_observables('query',
'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,
)
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 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.mode())
if not engineclass:
print "mode: '%s' is currently unimplemented" % self.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:])
query = subprocess.list2cmdline(filepaths)
self.model.set_query('')
self.set_mode(PATH)
self.model.set_query(query)
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.selected_row(widget)
if not selected or len(self.results) < row:
return
revision = self.results[row][0]
qtutils.set_clipboard(revision)
diff = gitcmds.commit_diff(revision)
self.view.commit_text.setText(diff)
def export_patch(self):
widget = self.view.commit_list
row, selected = qtutils.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))
def cherry_pick(self):
widget = self.view.commit_list
row, selected = qtutils.selected_row(widget)
if not selected or len(self.results) < row:
return
revision = self.results[row][0]
qtutils.log(*self.model.git.cherry_pick(revision,
with_stderr=True,
with_status=True))
def search_commits(model, parent):
def date(timespec):
return '%04d-%02d-%02d' % time.localtime(timespec)[:3]
# TODO subclass model for search only
model = SearchModel(cwd=model.git.worktree())
view = SearchView(parent)
ctl = SearchController(model, view)
ctl.set_mode(MESSAGE)
model.set_start_date(date(time.time()-(87640*31)))
model.set_end_date(date(time.time()+87640))
view.show()
+101
View File
@@ -0,0 +1,101 @@
"""This module provides a controller for selecting commits
"""
from PyQt4 import QtGui
from PyQt4.QtCore import SIGNAL
from cola import gitcmds
from cola import qtutils
from cola.prefs import diff_font
from cola.views.selectcommits import SelectCommitsView
from cola.qobserver import QObserver
from cola.controllers.createbranch import create_new_branch
from cola.main.model import MainModel
#+-------------------------------------------------------------
def select_commits(title, revs, summaries, multiselect=True):
"""Use the SelectCommitsView to select commits from a list."""
model = MainModel()
parent = qtutils.active_window()
view = SelectCommitsView(parent, qtutils.tr(title), multiselect=multiselect)
ctl = SelectCommitsController(model, view, revs, summaries)
return ctl.select_commits()
class SelectCommitsController(QObserver):
"""Select a commit from parallel rev and summary lists"""
def __init__(self, model, view, revs, summaries):
QObserver.__init__(self, model, view)
self.model.set_revisions(revs)
self.model.set_summaries(summaries)
self.connect(view.commit_list,
SIGNAL('itemSelectionChanged()'),
self.commit_sha1_selected)
view.commit_list.contextMenuEvent = self.context_menu_event
self.view.commit_text.setFont(diff_font())
def select_commits(self):
summaries = self.model.summaries
if not summaries:
msg = self.tr('No commits exist in this branch.')
qtutils.log(1, msg)
return []
qtutils.set_items(self.view.commit_list, summaries)
self.view.show()
if self.view.exec_() != QtGui.QDialog.Accepted:
return []
revs = self.model.revisions
list_widget = self.view.commit_list
return qtutils.selection_list(list_widget, revs)
def context_menu_event(self, event):
menu = QtGui.QMenu(self.view);
menu.addAction(self.tr('Checkout'), self.checkout_commit)
menu.addAction(self.tr('Create Branch here'), self.create_branch_at)
menu.addAction(self.tr('Cherry Pick'), self.cherry_pick)
menu.exec_(self.view.commit_list.mapToGlobal(event.pos()))
def checkout_commit(self):
row, selected = qtutils.selected_row(self.view.commit_list)
if not selected:
return
sha1 = self.model.revision_sha1(row)
qtutils.log(*self.model.git.checkout(sha1,
with_stderr=True,
with_status=True))
def create_branch_at(self):
row, selected = qtutils.selected_row(self.view.commit_list)
if not selected:
return
create_new_branch(revision=self.model.revision_sha1(row))
def cherry_pick(self):
row, selected = qtutils.selected_row(self.view.commit_list)
if not selected:
return
sha1 = self.model.revision_sha1(row)
qtutils.log(*self.model.git.cherry_pick(sha1,
with_stderr=True,
with_status=True))
def commit_sha1_selected(self):
row, selected = qtutils.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.revision_sha1(row)
self.view.revision.setText(sha1)
self.view.revision.selectAll()
# Lookup the sha1's commit
commit_diff = gitcmds.commit_diff(sha1)
self.view.commit_text.setText(commit_diff)
# Copy the sha1 into the clipboard
qtutils.set_clipboard(sha1)
+84
View File
@@ -0,0 +1,84 @@
"""This module provides core functions for handling unicode and UNIX quirks
OSX and others are known to interrupt system calls
http://en.wikipedia.org/wiki/PCLSRing
http://en.wikipedia.org/wiki/Unix_philosophy#Worse_is_better
The {read,write,wait}_nointr functions handle this situation
"""
import errno
# Some files are not in UTF-8; some other aren't in any codification.
# Remember that GIT doesn't care about encodings (saves binary data)
_encoding_tests = [
'utf-8',
'iso-8859-15',
'windows1252',
'ascii',
# <-- add encodings here
]
def decode(enc):
"""decode(encoded_string) returns an unencoded unicode string
"""
for encoding in _encoding_tests:
try:
return unicode(enc.decode(encoding))
except:
pass
# this shouldn't ever happen... FIXME
return unicode(enc)
def encode(unenc):
"""encode(unencoded_string) returns a string encoded in utf-8
"""
return unenc.encode('utf-8', 'replace')
def read_nointr(fh):
"""Read from a filehandle and retry when interrupted"""
while True:
try:
content = fh.read()
break
except IOError, e:
if e.errno == errno.EINTR:
continue
raise e
except OSError, e:
if e.errno == errno.EINTR:
continue
raise e
return content
def write_nointr(fh, content):
"""Write to a filehandle and retry when interrupted"""
while True:
try:
content = fh.write(content)
break
except IOError, e:
if e.errno == errno.EINTR:
continue
raise e
except OSError, e:
if e.errno == errno.EINTR:
continue
raise e
return content
def wait_nointr(proc):
"""Wait on a subprocess and retry when interrupted"""
while True:
try:
status = proc.wait()
break
except IOError, e:
if e.errno == errno.EINTR:
continue
raise e
except OSError, e:
if e.errno == errno.EINTR:
continue
raise e
return status
+34
View File
@@ -0,0 +1,34 @@
from PyQt4.QtCore import SIGNAL
from cola.qtutils import SLOT
from cola.cmdfactory import CommandFactory
class Controller(object):
def __init__(self, model, view):
self.model = model
self.view = view
self.commands = {}
self.factory = CommandFactory(model)
def add_command_directory(self, command_directory):
for signal, cmdclass in command_directory.items():
self.factory.add_command(signal, cmdclass)
def add_command(self, signal, handler=None):
if handler is None:
handler = self.do_wrapper(signal)
self.view.connect(self.view, SIGNAL(signal), handler)
def add_commands(self, command_directory, handler=None):
for signal, cmdclass in command_directory.items():
self.factory.add_command(signal, cmdclass)
self.add_command(signal, handler=handler)
def add_global_command(self, signal):
self.view.connect(self.view, SIGNAL(signal), SLOT(signal))
def do_wrapper(self, signal):
def do(*args, **opts):
return self.factory.do(signal, *args, **opts)
return do
+41
View File
@@ -0,0 +1,41 @@
import os
import sys
from PyQt4 import QtCore
if __name__ == '__main__':
# Find the source tree
src = os.path.join(os.path.dirname(__file__), '..', '..')
sys.path.insert(1, os.path.abspath(src))
from cola import qtutils
from cola.dag.view import DAGView
from cola.dag.model import DAG
from cola.dag.controller import DAGController
def git_dag(model, opts=None, args=None):
"""Return a pre-populated git DAG widget."""
dag = DAG(model.currentbranch, 1000)
dag.set_options(opts, args)
view = DAGView(model, dag, qtutils.active_window())
ctl = DAGController(dag, view)
view.show()
view.raise_()
if dag.ref:
view.thread.start(QtCore.QThread.LowPriority)
return ctl
if __name__ == "__main__":
import cola
from cola import app
model = cola.model()
model.set_worktree(os.getcwd())
model.update_status()
app = app.ColaApplication(sys.argv)
ctl = git_dag(model)
sys.exit(app.exec_())
+8
View File
@@ -0,0 +1,8 @@
from cola.ctrl import Controller
from cola.dag.model import command_directory
class DAGController(Controller):
def __init__(self, model, view):
Controller.__init__(self, model, view)
self.add_commands(command_directory)
+264
View File
@@ -0,0 +1,264 @@
import subprocess
import cola
from cola import core
from cola import git
from cola import signals
from cola import utils
from cola.cmds import BaseCommand
from cola.observable import Observable
# put subject at the end b/c it can contain
# any number of funky characters
logfmt = 'format:%H%x01%P%x01%d%x01%an%x01%aD%x01%s'
git = git.instance()
archive = 'archive'
class CommitFactory(object):
root_generation = 0
_commits = {}
@classmethod
def reset(cls):
cls._commits.clear()
cls.root_generation = 0
@classmethod
def new(cls, sha1=None, log_entry=None):
if not sha1 and log_entry:
sha1 = log_entry[:40]
try:
commit = cls._commits[sha1]
if log_entry and not commit.parsed:
commit.parse(log_entry)
cls.root_generation = max(commit.generation,
cls.root_generation)
except KeyError:
commit = Commit(sha1=sha1,
log_entry=log_entry)
if not log_entry:
cls.root_generation += 1
commit.generation = max(commit.generation,
cls.root_generation)
cls._commits[sha1] = commit
return commit
class DAG(Observable):
ref_updated = 'ref_updated'
count_updated = 'count_updated'
def __init__(self, ref, count):
Observable.__init__(self)
self.ref = ref
self.count = count
self.overrides = {}
def set_ref(self, ref):
changed = ref != self.ref
self.ref = ref
self.notify_message_observers(self.ref_updated)
return changed
def set_count(self, count):
changed = count != self.count
self.count = count
self.notify_message_observers(self.count_updated)
return changed
def set_options(self, opts, args):
if opts is not None:
if self.set_count(opts.count):
self.overrides['count'] = opts.count
if args is not None:
ref = subprocess.list2cmdline([core.decode(a) for a in args])
if self.set_ref(ref):
self.overrides['ref'] = ref
def overridden(self, opt):
return opt in self.overrides
class Commit(object):
root_generation = 0
__slots__ = ('sha1',
'subject',
'parents',
'children',
'tags',
'author',
'authdate',
'generation',
'parsed')
def __init__(self, sha1=None, log_entry=None):
self.sha1 = sha1
self.subject = None
self.parents = []
self.children = []
self.tags = set()
self.author = None
self.authdate = None
self.parsed = False
self.generation = CommitFactory.root_generation
if log_entry:
self.parse(log_entry)
def parse(self, log_entry):
self.sha1 = log_entry[:40]
(parents, tags, author, authdate, subject) = \
log_entry[41:].split(chr(0x01), 5)
if subject:
self.subject = core.decode(subject)
if parents:
generation = None
for parent_sha1 in parents.split(' '):
parent = CommitFactory.new(sha1=parent_sha1)
parent.children.append(self)
if generation is None:
generation = parent.generation+1
self.parents.append(parent)
generation = max(parent.generation+1, generation)
self.generation = generation
if tags:
for tag in tags[2:-1].split(', '):
if tag.startswith('tag: '):
tag = tag[5:] # tag: refs/
elif tag.startswith('refs/remotes/'):
tag = tag[13:] # refs/remotes/
elif tag.startswith('refs/heads/'):
tag = tag[11:] # refs/heads/
if tag.endswith('/HEAD'):
continue
self.tags.add(core.decode(tag))
if author:
self.author = core.decode(author)
if authdate:
self.authdate = authdate
self.parsed = True
return self
def __str__(self):
return self.sha1
def __repr__(self):
return ("{\n"
" sha1: " + self.sha1 + "\n"
" subject: " + self.subject + "\n"
" author: " + self.author + "\n"
" authdate: " + self.authdate + "\n"
" parents: [" + ', '.join(self.parents) + "]\n"
" tags: [" + ', '.join(self.tags) + "]\n"
"}")
class RepoReader(object):
def __init__(self, dag, git=git):
self.dag = dag
self.git = git
self._proc = None
self._objects = {}
self._cmd = ['git', 'log',
'--topo-order',
'--reverse',
'--pretty='+logfmt]
self._cached = False
"""Indicates that all data has been read"""
self._idx = -1
"""Index into the cached commits"""
self._topo_list = []
"""List of commits objects in topological order"""
cached = property(lambda self: self._cached)
"""Return True when no commits remain to be read"""
def __len__(self):
return len(self._topo_list)
def reset(self):
CommitFactory.reset()
if self._proc:
self._topo_list = []
self._proc.kill()
self._proc = None
self._cached = False
def __iter__(self):
if self._cached:
return self
self.reset()
return self
def next(self):
if self._cached:
try:
self._idx += 1
return self._topo_list[self._idx]
except IndexError:
self._idx = -1
raise StopIteration
if self._proc is None:
ref_args = utils.shell_split(self.dag.ref)
cmd = self._cmd + ['-%d' % self.dag.count] + ref_args
self._proc = utils.start_command(cmd)
self._topo_list = []
log_entry = self._proc.stdout.readline().rstrip()
if not log_entry:
del self._proc
self._cached = True
self._proc = None
raise StopIteration
sha1 = log_entry[:40]
try:
return self._objects[sha1]
except KeyError:
c = CommitFactory.new(log_entry=log_entry)
self._objects[c.sha1] = c
self._topo_list.append(c)
return c
def __getitem__(self, sha1):
return self._objects[sha1]
def items(self):
return self._objects.items()
class Archive(BaseCommand):
def __init__(self, ref, fmt, prefix, filename):
BaseCommand.__init__(self)
self.ref = ref
self.fmt = fmt
self.prefix = prefix
self.filename = filename
def do(self):
fp = open(core.encode(self.filename), 'wb')
cmd = ['git', 'archive', '--format='+self.fmt]
if self.fmt in ('tgz', 'tar.gz'):
cmd.append('-9')
if self.prefix:
cmd.append('--prefix=' + self.prefix)
cmd.append(self.ref)
proc = utils.start_command(cmd, stdout=fp)
out, err = proc.communicate()
fp.close()
if not out:
out = ''
if err:
out += err
status = proc.returncode
cola.notifier().broadcast(signals.log_cmd, status, out)
command_directory = {
archive: Archive,
}
+1351
View File
File diff suppressed because it is too large Load Diff
+53
View File
@@ -0,0 +1,53 @@
__all__ = ('decorator', 'deprecated', 'memoize')
def decorator(caller, func=None):
"""
decorator(caller) converts a caller function into a decorator;
decorator(caller, func) decorates a function using a caller.
"""
if func is None: # returns a decorator
def _decorator(f, *args, **opts):
def _caller(*args, **opts):
return caller(f, *args, **opts)
return _caller
return _decorator
else: # returns a decorated function
def _decorator(*args, **opts):
return caller(func, *args, **opts)
return _decorator
@decorator
def deprecated(func, *args, **kw):
"A decorator for deprecated functions"
import warnings
warnings.warn('Calling deprecated function %r' % func.__name__,
DeprecationWarning, stacklevel=3)
return func(*args, **kw)
def memoize(func):
"""
A decorator for memoizing function calls
http://en.wikipedia.org/wiki/Memoization
"""
func.cache = {}
return decorator(_memoize, func)
def _memoize(func, *args, **opts):
"""Implements memoized cache lookups"""
if opts: # frozenset is used to ensure hashability
key = args, frozenset(opts.items())
else:
key = args
cache = func.cache # attribute added by memoize
try:
return cache[key]
except KeyError:
cache[key] = result = func(*args, **opts)
return result
+251
View File
@@ -0,0 +1,251 @@
import os
import re
from cola import utils
from cola import gitcmds
class DiffParser(object):
"""Handles parsing diff for use by the interactive index editor."""
def __init__(self, model, filename='',
cached=True, branch=None, reverse=False):
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) = gitcmds.diff_helper(filename=filename,
branch=branch,
with_diff_header=True,
cached=cached and not bool(branch),
reverse=cached or bool(branch) or reverse)
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 = \
gitcmds.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):
"""Writes a new diff corresponding to the user's selection."""
if not noop and which < len(self.diffs):
diff = self.diffs[which]
utils.write(filename, self.header + '\n' + diff + '\n')
return True
else:
return False
def diffs(self):
"""Returns the list of diffs."""
return self._diffs
def diff_subset(self, diff, start, end):
"""Processes the diffs and returns a selected subset from that diff.
"""
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 spans(self):
"""Returns the line spans of each hunk."""
return self._diff_spans
def offsets(self):
"""Returns the offsets."""
return self._diff_offsets
def set_diff_to_offset(self, offset):
"""Sets the diff selection to be the hunk at a particular offset."""
self.offset = offset
self.diffs, self.selected = self.diff_for_offset(offset)
def set_diffs_to_range(self, start, end):
"""Sets the diff selection to be a range of hunks."""
self.start = start
self.end = end
self.diffs, self.selected = self.diffs_for_range(start,end)
def diff_for_offset(self, offset):
"""Returns the hunks for a particular offset."""
for idx, diff_offset in enumerate(self._diff_offsets):
if offset < diff_offset:
return (['\n'.join(self._diffs[idx])], [idx])
return ([],[])
def diffs_for_range(self, start, end):
"""Returns the hunks for a selected range."""
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):
"""Parses a diff and extracts headers, offsets, hunks, etc.
"""
total_offset = 0
self._idx = -1
self._headers = []
for idx, line in enumerate(diff.split('\n')):
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,
apply_to_worktree=False):
"""Processes a diff selection and applies changes to git."""
if selection:
# qt destroys \r\n and makes it \n with no way of going back.
# boo! we work around that here.
# I think this was win32-specific. We might want to do
# this on win32 only (TODO verify)
if selection not in self.fwd_diff:
special_selection = selection.replace('\n', '\r\n')
if special_selection in self.fwd_diff:
selection = special_selection
else:
return 0, ''
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
output = ''
status = 0
# Process diff selection only
if selected:
for idx in self.selected:
contents = self.diff_subset(idx, start, end)
if not contents:
continue
tmpfile = self.model.tmp_filename()
utils.write(tmpfile, contents)
if apply_to_worktree:
stat, out = self.model.apply_diff_to_worktree(tmpfile)
output += out
status = max(status, stat)
else:
stat, out = self.model.apply_diff(tmpfile)
output += out
status = max(status, stat)
os.unlink(tmpfile)
# Process a complete hunk
else:
for idx, diff in enumerate(self.diffs):
tmpfile = self.model.tmp_filename()
if not self.write_diff(tmpfile,idx):
continue
if apply_to_worktree:
stat, out = self.model.apply_diff_to_worktree(tmpfile)
output += out
status = max(status, stat)
else:
stat, out = self.model.apply_diff(tmpfile)
output += out
status = max(status, stat)
os.unlink(tmpfile)
return status, output
+114
View File
@@ -0,0 +1,114 @@
from PyQt4 import QtGui
from PyQt4 import QtCore
from PyQt4.QtCore import SIGNAL
from cola import utils
from cola import qtutils
from cola import gitcmds
from cola.widgets import defs
def launch(args):
"""Launches 'git difftool' with args"""
difftool_args = ['git', 'difftool', '--no-prompt']
difftool_args.extend(args)
utils.fork(difftool_args)
def diff_commits(parent, a, b):
dlg = FileDiffDialog(parent, a, b)
dlg.show()
dlg.raise_()
return dlg.exec_() == QtGui.QDialog.Accepted
class FileDiffDialog(QtGui.QDialog):
def __init__(self, parent, a, b):
QtGui.QDialog.__init__(self, parent)
self.a = a
self.b = b
self.setWindowTitle('Select File(s)')
self._tree = QtGui.QTreeWidget(self)
self._tree.setAlternatingRowColors(True)
self._tree.setRootIsDecorated(False)
self._tree.setSelectionMode(self._tree.ExtendedSelection)
self._tree.setUniformRowHeights(True)
self._tree.setAllColumnsShowFocus(True)
self._tree.setHeaderHidden(True)
self._diff_btn = QtGui.QPushButton('Compare')
self._diff_btn.setIcon(qtutils.ok_icon())
self._diff_btn.setEnabled(False)
self._close_btn = QtGui.QPushButton('Close')
self._close_btn.setIcon(qtutils.close_icon())
self._button_layt = QtGui.QHBoxLayout()
self._button_layt.setMargin(0)
self._button_layt.addStretch()
self._button_layt.addWidget(self._diff_btn)
self._button_layt.addWidget(self._close_btn)
self._layt = QtGui.QVBoxLayout()
self._layt.setMargin(defs.margin)
self._layt.setSpacing(defs.spacing)
self._layt.addWidget(self._tree)
self._layt.addLayout(self._button_layt)
self.setLayout(self._layt)
qtutils.add_close_action(self)
self.connect(self._tree, SIGNAL('itemSelectionChanged()'),
self._tree_selection_changed)
self.connect(self._tree,
SIGNAL('itemDoubleClicked(QTreeWidgetItem*,int)'),
self._tree_double_clicked)
self.connect(self._diff_btn, SIGNAL('clicked()'), self.diff)
self.connect(self._close_btn, SIGNAL('clicked()'), self.close)
self.diff_arg = '%s..%s' % (self.a, self.b)
self.resize(720, 420)
def exec_(self):
filenames = gitcmds.diff_filenames(self.diff_arg)
if not filenames:
details = ('"git diff --name-only %s" returned an empty list' %
self.diff_arg)
self.hide()
qtutils.information('git cola',
message='No changes to diff',
details=details)
self.close()
return self.Accepted
icon = qtutils.file_icon()
items = []
for filename in filenames:
item = QtGui.QTreeWidgetItem()
item.setIcon(0, icon)
item.setText(0, filename)
item.setData(0, QtCore.Qt.UserRole, QtCore.QVariant(filename))
items.append(item)
self._tree.addTopLevelItems(items)
return QtGui.QDialog.exec_(self)
def _tree_selection_changed(self):
self._diff_btn.setEnabled(bool(self._tree.selectedItems()))
def _tree_double_clicked(self, item, column):
path = item.data(0, QtCore.Qt.UserRole).toPyObject()
launch([self.diff_arg, '--', unicode(path)])
def diff(self):
items = self._tree.selectedItems()
if not items:
return
paths = [i.data(0, QtCore.Qt.UserRole).toPyObject() for i in items]
for path in paths:
launch([self.diff_arg, '--', unicode(path)])
+11
View File
@@ -0,0 +1,11 @@
"""Provides exception classes used by cola"""
class ColaError(StandardError):
"""The base class of all cola exceptions"""
pass
class UsageError(ColaError):
"""Exception class for usage errors."""
def __init__(self, title, message):
ColaError.__init__(self, message)
self.title = title
self.message = message
+300
View File
@@ -0,0 +1,300 @@
import os
import sys
import errno
import subprocess
import threading
cmdlock = threading.Lock()
import cola
from cola import core
from cola import signals
from cola.decorators import memoize
GIT_COLA_TRACE = os.getenv('GIT_COLA_TRACE', '')
def dashify(string):
return string.replace('_', '-')
class Git(object):
"""
The Git class manages communication with the Git binary
"""
def __init__(self):
self._git_cwd = None #: The working directory used by execute()
self.set_worktree(os.getcwd())
def set_worktree(self, path):
self._git_dir = path
self._worktree = None
self.worktree()
def worktree(self):
if self._worktree:
return self._worktree
self.git_dir()
if self._git_dir:
curdir = self._git_dir
else:
curdir = os.getcwd()
if self._is_git_dir(os.path.join(curdir, '.git')):
return curdir
# Handle bare repositories
if (len(os.path.basename(curdir)) > 4
and curdir.endswith('.git')):
return curdir
if 'GIT_WORK_TREE' in os.environ:
self._worktree = os.getenv('GIT_WORK_TREE')
if not self._worktree or not os.path.isdir(self._worktree):
if self._git_dir:
gitparent = os.path.join(os.path.abspath(self._git_dir), '..')
self._worktree = os.path.abspath(gitparent)
self.set_cwd(self._worktree)
return self._worktree
def is_valid(self):
return self._git_dir and self._is_git_dir(self._git_dir)
def git_path(self, *paths):
return os.path.join(self.git_dir(), *paths)
def git_dir(self):
if self.is_valid():
return self._git_dir
if 'GIT_DIR' in os.environ:
self._git_dir = os.getenv('GIT_DIR')
if self._git_dir:
curpath = os.path.abspath(self._git_dir)
else:
curpath = os.path.abspath(os.getcwd())
# Search for a .git directory
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):
"""From git's setup.c:is_git_directory()."""
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
def set_cwd(self, path):
"""Sets the current directory."""
self._git_cwd = path
def __getattr__(self, name):
if name[:1] == '_':
raise AttributeError(name)
return lambda *args, **kwargs: self._call_process(name, *args, **kwargs)
@staticmethod
def execute(command,
cwd=None,
istream=None,
with_exceptions=False,
with_raw_output=False,
with_status=False,
with_stderr=False,
cola_trace=GIT_COLA_TRACE):
"""
Execute a command and returns its output
``command``
The command argument list to execute
``istream``
Readable filehandle passed to subprocess.Popen.
``cwd``
The working directory when running commands.
Default: os.getcwd()
``with_status``
Whether to return a (status, unicode(output)) tuple.
``with_stderr``
Whether to include stderr in the output stream
``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
unicode(stdout) # Default
unicode(stdout+stderr) # with_stderr=True
tuple(int(status), unicode(output)) # with_status=True
"""
# Allow the user to have the command executed in their working dir.
if not cwd:
cwd = os.getcwd()
extra = {}
if sys.platform == 'win32':
command = map(replace_carot, command)
extra = {'shell': True}
# Start the process
# Guard against thread-unsafe .git/index.lock files
cmdlock.acquire()
# Some systems (e.g. darwin) interrupt system calls
count = 0
while count < 13:
try:
proc = subprocess.Popen(command,
cwd=cwd,
stdin=istream,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE,
**extra)
# Wait for the process to return
out, err = proc.communicate()
status = proc.returncode
break
except OSError, e:
if e.errno == errno.EINTR or e.errno == errno.ENOMEM:
count += 1
continue
cmdlock.release()
raise
except:
cmdlock.release()
raise
# Let the next thread in
cmdlock.release()
output = with_stderr and (out+err) or out
if not with_raw_output:
output = output.rstrip('\n')
if cola_trace == 'trace':
msg = 'trace: ' + subprocess.list2cmdline(command)
cola.notifier().broadcast(signals.log_cmd, status, msg)
elif cola_trace == 'full':
if output:
print "%s -> %d: '%s'" % (command, status, output)
else:
print "%s -> %d" % (command, status)
elif cola_trace:
print ' '.join(command)
# Allow access to the command's status code
if with_status:
return (status, output)
else:
return output
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 _call_process(self, cmd, *args, **kwargs):
"""
Run the given git command with the specified arguments and return
the result as a String
``cmd``
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 they'll end up in args, which is bad.
_kwargs = dict(cwd=self._git_cwd)
execute_kwargs = ('cwd', 'istream',
'with_exceptions',
'with_raw_output',
'with_status',
'with_stderr')
for kwarg in execute_kwargs:
if kwarg in kwargs:
_kwargs[kwarg] = kwargs.pop(kwarg)
# Prepare the argument list
opt_args = self.transform_kwargs(**kwargs)
ext_args = map(core.encode, args)
args = opt_args + ext_args
call = ['git', dashify(cmd)]
call.extend(args)
return self.execute(call, **_kwargs)
def replace_carot(cmd_arg):
"""
Guard against the windows command shell.
In the Windows shell, a carat character (^) may be used for
line continuation. To guard against this, escape the carat
by using two of them.
http://technet.microsoft.com/en-us/library/cc723564.aspx
"""
return cmd_arg.replace('^', '^^')
@memoize
def instance():
"""Return the Git singleton"""
return Git()
git = instance()
"""
Git command singleton
>>> from cola.git import git
>>> 'git' == git.version()[:3]
True
"""
+232
View File
@@ -0,0 +1,232 @@
import os
import copy
import fnmatch
from cola import core
from cola import git
from cola import observable
from cola.decorators import memoize
@memoize
def instance():
"""Return a static GitConfig instance."""
return GitConfig()
def _stat_info():
# Try /etc/gitconfig as a fallback for the system config
userconfig = os.path.expanduser(os.path.join('~', '.gitconfig'))
paths = (('system', '/etc/gitconfig'),
('user', core.decode(userconfig)),
('repo', core.decode(git.instance().git_path('config'))))
statinfo = []
for category, path in paths:
try:
statinfo.append((category, path, os.stat(path).st_mtime))
except OSError:
continue
return statinfo
def _cache_key():
# Try /etc/gitconfig as a fallback for the system config
userconfig = os.path.expanduser(os.path.join('~', '.gitconfig'))
paths = ('/etc/gitconfig',
userconfig,
git.instance().git_path('config'))
mtimes = []
for path in paths:
try:
mtimes.append(os.stat(path).st_mtime)
except OSError:
continue
return mtimes
class GitConfig(observable.Observable):
"""Encapsulate access to git-config values."""
message_user_config_changed = 'user_config_changed'
message_repo_config_changed = 'repo_config_changed'
def __init__(self):
observable.Observable.__init__(self)
self.git = git.instance()
self._system = {}
self._user = {}
self._repo = {}
self._all = {}
self._cache_key = None
self._configs = []
self._config_files = {}
self._find_config_files()
def reset(self):
self._system = {}
self._user = {}
self._repo = {}
self._all = {}
self._cache_key = None
self._configs = []
self._config_files = {}
self._find_config_files()
def user(self):
return copy.deepcopy(self._user)
def repo(self):
return copy.deepcopy(self._repo)
def all(self):
return copy.deepcopy(self._all)
def _find_config_files(self):
"""
Classify git config files into 'system', 'user', and 'repo'.
Populates self._configs with a list of the files in
reverse-precedence order. self._config_files is populated with
{category: path} where category is one of 'system', 'user', or 'repo'.
"""
# Try the git config in git's installation prefix
statinfo = _stat_info()
self._configs = map(lambda x: x[1], statinfo)
self._config_files = {}
for (cat, path, mtime) in statinfo:
self._config_files[cat] = path
def update(self):
"""Read config values from git."""
if self._cached():
return
self._read_configs()
def _cached(self):
"""
Return True when the cache matches.
Updates the cache and returns False when the cache does not match.
"""
cache_key = _cache_key()
if not self._cache_key or cache_key != self._cache_key:
self._cache_key = cache_key
return False
return True
def _read_configs(self):
"""Read git config value into the system, user and repo dicts."""
self._system = {}
self._user = {}
self._repo = {}
self._all = {}
if 'system' in self._config_files:
self._system = self.read_config(self._config_files['system'])
if 'user' in self._config_files:
self._user = self.read_config(self._config_files['user'])
if 'repo' in self._config_files:
self._repo = self.read_config(self._config_files['repo'])
self._all = {}
for dct in (self._system, self._user, self._repo):
self._all.update(dct)
def read_config(self, path):
"""Return git config data from a path as a dictionary."""
dest = {}
args = ('--null', '--file', path, '--list')
config_lines = self.git.config(*args).split('\0')
for line in config_lines:
try:
k, v = line.split('\n', 1)
except ValueError:
# the user has an invalid entry in their git config
if not line:
continue
k = line
v = 'true'
v = core.decode(v)
if v in ('true', 'yes'):
v = True
elif v in ('false', 'no'):
v = False
else:
try:
v = int(v)
except ValueError:
pass
dest[k.lower()] = v
return dest
def get(self, key, default=None, source=None):
"""Return the string value for a config key."""
self.update()
return self._all.get(key, default)
def get_user(self, key, default=None):
self.update()
return self._user.get(key, default)
def get_repo(self, key, default=None):
self.update()
return self._repo.get(key, default)
def python_to_git(self, value):
if type(value) is bool:
if value:
return 'true'
else:
return 'false'
if type(value) is int:
return unicode(value)
return value
def set_user(self, key, value):
msg = self.message_user_config_changed
self.git.config('--global', key, self.python_to_git(value))
self.update()
self.notify_message_observers(msg, key, value)
def set_repo(self, key, value):
msg = self.message_repo_config_changed
self.git.config(key, self.python_to_git(value))
self.update()
self.notify_message_observers(msg, key, value)
def find(self, pat):
result = {}
for key, val in self._all.items():
if fnmatch.fnmatch(key, pat):
result[key] = val
return result
def get_encoding(self, default='utf-8'):
return self.get('gui.encoding', default=default)
guitool_opts = ('cmd', 'needsfile', 'noconsole', 'norescan', 'confirm',
'argprompt', 'revprompt', 'revunmerged', 'title', 'prompt')
def get_guitool_opts(self, name):
"""Return the guitool.<name> namespace as a dict"""
keyprefix = 'guitool.' + name + '.'
opts = {}
for cfg in self.guitool_opts:
value = self.get(keyprefix + cfg)
if value is None:
continue
opts[cfg] = value
return opts
def get_guitool_names(self):
cmds = []
guitools = self.find('guitool.*.cmd')
for name, cmd in guitools.items():
name = name[len('guitool.'):-len('.cmd')]
cmds.append(name)
return cmds
+637
View File
@@ -0,0 +1,637 @@
"""Provides commands and queries for Git."""
import os
import re
from cStringIO import StringIO
from cola import core
from cola import git
from cola import gitcfg
from cola import errors
from cola import utils
from cola import version
from cola.compat import set
git = git.instance()
config = gitcfg.instance()
class InvalidRepositoryError(StandardError):
pass
def default_remote(config=None):
"""Return the remote tracked by the current branch."""
if config is None:
config = gitcfg.instance()
return config.get('branch.%s.remote' % current_branch())
def diff_filenames(arg, git=git):
"""Return a list of filenames that have been modified"""
diff_zstr = git.diff(arg, name_only=True, z=True,
**_common_diff_opts()).rstrip('\0')
return [core.decode(f) for f in diff_zstr.split('\0') if f]
def all_files(git=git):
"""Return the names of all files in the repository"""
return [core.decode(f)
for f in git.ls_files(z=True)
.strip('\0').split('\0') if f]
class _current_branch:
"""Cache for current_branch()"""
key = None
value = None
def clear_cache():
_current_branch.key = None
def current_branch(git=git):
"""Return the current branch"""
head = git.git_path('HEAD')
try:
key = os.stat(head).st_mtime
if _current_branch.key == key:
return _current_branch.value
except OSError, e:
pass
data = git.rev_parse('HEAD', with_stderr=True, symbolic_full_name=True)
if data.startswith('fatal:'):
# git init -- read .git/HEAD. We could do this unconditionally
# and avoid the subprocess call. It's probably time to start
# using dulwich.
data = _read_git_head(head)
for refs_prefix in ('refs/heads/', 'refs/remotes/'):
if data.startswith(refs_prefix):
value = core.decode(data[len(refs_prefix):])
_current_branch.key = key
_current_branch.value = value
return value
# Detached head
return data
def _read_git_head(head, default='master', git=git):
"""Pure-python .git/HEAD reader"""
# Legacy .git/HEAD symlinks
if os.path.islink(head):
refs_heads = os.path.realpath(git.git_path('refs', 'heads'))
path = os.path.abspath(head).replace('\\', '/')
if path.startswith(refs_heads + '/'):
return path[len(refs_heads)+1:]
# Common .git/HEAD "ref: refs/heads/master" file
elif os.path.isfile(head):
data = utils.slurp(core.decode(head)).rstrip()
ref_prefix = 'ref: '
if data.startswith(ref_prefix):
return data[len(ref_prefix):]
# Detached head
return data
return default
def branch_list(remote=False):
"""
Return a list of local or remote branches
This explicitly removes HEAD from the list of remote branches.
"""
if remote:
return for_each_ref_basename('refs/remotes')
else:
return for_each_ref_basename('refs/heads')
def for_each_ref_basename(refs, git=git):
"""Return refs starting with 'refs'."""
git_output = git.for_each_ref(refs, format='%(refname)')
output = core.decode(git_output).splitlines()
non_heads = filter(lambda x: not x.endswith('/HEAD'), output)
return map(lambda x: x[len(refs) + 1:], non_heads)
def all_refs(split=False, git=git):
"""Return a tuple of (local branches, remote branches, tags)."""
local_branches = []
remote_branches = []
tags = []
triple = lambda x, y: (x, len(x) + 1, y)
query = (triple('refs/tags', tags),
triple('refs/heads', local_branches),
triple('refs/remotes', remote_branches))
cmdout = core.decode(git.for_each_ref(format='%(refname)'))
for ref in cmdout.splitlines():
for prefix, prefix_len, dst in query:
if ref.startswith(prefix) and not ref.endswith('/HEAD'):
dst.append(ref[prefix_len:])
continue
if split:
return local_branches, remote_branches, tags
else:
return local_branches + remote_branches + tags
def tracked_branch(branch=None, config=None):
"""Return the remote branch associated with 'branch'."""
if config is None:
config = gitcfg.instance()
if branch is None:
branch = current_branch()
remote = config.get('branch.%s.remote' % branch)
if not remote:
return None
merge_ref = config.get('branch.%s.merge' % branch)
if not merge_ref:
return None
refs_heads = 'refs/heads/'
if merge_ref.startswith(refs_heads):
return remote + '/' + merge_ref[len(refs_heads):]
return None
def untracked_files(git=git):
"""Returns a sorted list of untracked files."""
ls_files = git.ls_files(z=True,
others=True,
exclude_standard=True)
return [core.decode(f) for f in ls_files.split('\0') if f]
def tag_list():
"""Return a list of tags."""
tags = for_each_ref_basename('refs/tags')
tags.reverse()
return tags
def commit_diff(sha1, git=git):
commit = git.show(sha1)
first_newline = commit.index('\n')
if commit[first_newline+1:].startswith('Merge:'):
return (core.decode(commit) + '\n\n' +
core.decode(diff_helper(commit=sha1,
cached=False,
suppress_header=False)))
else:
return core.decode(commit)
def _common_diff_opts(config=config):
submodule = version.check('diff-submodule', version.git_version())
return {
'patience': True,
'submodule': submodule,
'no_color': True,
'no_ext_diff': True,
'with_raw_output': True,
'with_stderr': True,
'unified': config.get('gui.diffcontext', 3),
}
def sha1_diff(sha1, git=git):
return core.decode(git.diff(sha1 + '^!', **_common_diff_opts()))
def diff_info(sha1, git=git, merge=True):
if merge:
mergefmt = 'Merge: %P%n'
else:
mergefmt = ''
log = git.log('-1',
'--pretty=format:'
'commit %H%n' +
mergefmt +
'Author: %aN <%aE>%n'
'Date: %aD%n'
'%n%s%n%n%b',
sha1)
return core.decode(log) + '\n\n' + sha1_diff(sha1)
def diff_helper(commit=None,
branch=None,
ref=None,
endref=None,
filename=None,
cached=True,
with_diff_header=False,
suppress_header=True,
reverse=False,
git=git):
"Invokes git diff on a filepath."
if commit:
ref, endref = commit+'^', commit
argv = []
if ref and endref:
argv.append('%s..%s' % (ref, endref))
elif ref:
for r in ref.strip().split():
argv.append(r)
elif branch:
argv.append(branch)
if filename:
argv.append('--')
if type(filename) is list:
argv.extend(filename)
else:
argv.append(filename)
start = False
del_tag = 'deleted file mode '
headers = []
deleted = cached and not os.path.exists(core.encode(filename))
diffoutput = git.diff(R=reverse, M=True, cached=cached,
*argv, **_common_diff_opts())
# Handle 'git init'
if diffoutput.startswith('fatal:'):
if with_diff_header:
return ('', '')
else:
return ''
if diffoutput.startswith('Submodule'):
if with_diff_header:
return ('', diffoutput)
else:
return diffoutput
output = StringIO()
diff = diffoutput.split('\n')
for line in map(core.decode, diff):
if not start and '@@' == line[:2] and '@@' in line[2:]:
start = True
if start or (deleted and del_tag in line):
output.write(core.encode(line) + '\n')
else:
if with_diff_header:
headers.append(core.encode(line))
elif not suppress_header:
output.write(core.encode(line) + '\n')
result = core.decode(output.getvalue())
output.close()
if with_diff_header:
return('\n'.join(headers), result)
else:
return result
def format_patchsets(to_export, revs, output='patches'):
"""
Group contiguous revision selection into patchsets
Exists to handle multi-selection.
Multiple disparate ranges in the revision selection
are grouped into continuous lists.
"""
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
status = 0
for patchset in patches_to_export:
newstatus, out = export_patchset(patchset[0],
patchset[-1],
output='patches',
n=len(patchset) > 1,
thread=True,
patch_with_stat=True)
outlines.append(out)
if status == 0:
status += newstatus
return (status, '\n'.join(outlines))
def export_patchset(start, end, output='patches', **kwargs):
"""Export patches from start^ to end."""
return git.format_patch('-o', output, start + '^..' + end,
with_stderr=True,
with_status=True,
**kwargs)
def unstage_paths(args, head='HEAD'):
status, output = git.reset(head, '--', with_stderr=True, with_status=True,
*set(args))
if status != 128:
return (status, output)
# handle git init: we have to use 'git rm --cached'
# detect this condition by checking if the file is still staged
status, output = git.update_index('--',
force_remove=True,
with_status=True,
with_stderr=True,
*set(args))
return (status, output)
def worktree_state(head='HEAD', staged_only=False):
"""Return a tuple of files in various states of being
Can be staged, unstaged, untracked, unmerged, or changed
upstream.
"""
state = worktree_state_dict(head=head, staged_only=staged_only)
return(state.get('staged', []),
state.get('modified', []),
state.get('unmerged', []),
state.get('untracked', []),
state.get('upstream_changed', []))
def worktree_state_dict(head='HEAD',
staged_only=False,
update_index=False,
git=git):
"""Return a dict of files in various states of being
:rtype: dict, keys are staged, unstaged, untracked, unmerged,
changed_upstream, and submodule.
"""
if update_index:
git.update_index(refresh=True)
if staged_only:
return _branch_status(head)
staged, unmerged, submodules = diff_index(head)
modified, more_submods = diff_worktree()
submodules = submodules.union(more_submods)
untracked = untracked_files()
# Look for upstream modified files if this is a tracking branch
upstream_changed = diff_upstream(head)
# Keep stuff sorted
staged.sort()
modified.sort()
unmerged.sort()
untracked.sort()
upstream_changed.sort()
return {'staged': staged,
'modified': modified,
'unmerged': unmerged,
'untracked': untracked,
'upstream_changed': upstream_changed,
'submodules': submodules}
def diff_index(head):
submodules = set()
staged = []
unmerged = []
output = git.diff_index(head, z=True, cached=True, with_stderr=True)
if output.startswith('fatal:'):
# handle git init
return all_files(), unmerged, submodules
while output:
rest, output = output.split('\0', 1)
name, output = output.split('\0', 1)
status = rest[-1]
name = core.decode(name)
if '160000' in rest[1:14]:
submodules.add(name)
elif status in 'DAM':
staged.append(name)
elif status == 'U':
unmerged.append(name)
return staged, unmerged, submodules
def diff_worktree():
modified = []
submodules = set()
output = git.diff_files(z=True, with_stderr=True)
if output.startswith('fatal:'):
# handle git init
ls_files = git.ls_files(modified=True, z=True)[:-1].split('\0')
modified = [core.decode(f) for f in ls_files if f]
return modified, submodules
while output:
rest, output = output.split('\0', 1)
name, output = output.split('\0', 1)
status = rest[-1]
name = core.decode(name)
if '160000' in rest[1:14]:
submodules.add(name)
elif status in 'DAM':
modified.append(name)
return modified, submodules
def diff_upstream(head):
tracked = tracked_branch()
if not tracked:
return []
diff_expr = merge_base_to(head, tracked)
output = git.diff(diff_expr, name_only=True, z=True,
**_common_diff_opts())
if output.startswith('fatal:'):
return []
return [core.decode(n) for n in output.split('\0') if n]
def partial_worktree_state_dict(files, head='HEAD'):
states = []
for path in files:
output = git.status(path, porcelain=True)
if output == "" or output[1] == " ":
states.append(('unmodified', path))
status = None
else:
status = output[1]
if status == "M" or status == "D" or status == "R":
states.append(('modified', path))
elif status == "?":
states.append(('untracked', path))
return states
def _branch_status(branch, git=git):
"""
Returns a tuple of staged, unstaged, untracked, and unmerged files
This shows only the changes that were introduced in branch
"""
status, output = git.diff(name_only=True,
M=True, z=True,
with_status=True,
*branch.strip().split(),
**_common_diff_opts())
if status != 0:
return {}
staged = map(core.decode, [n for n in output.split('\0') if n])
return {'staged': staged,
'upstream_changed': staged}
def merge_base_to(head, ref):
"""Given `ref`, return $(git merge-base ref HEAD)..ref."""
base = git.merge_base(head, ref)
return '%s..%s' % (base, ref)
def merge_base_parent(branch):
tracked = tracked_branch(branch=branch)
if tracked:
return '%s..%s' % (tracked, branch)
return 'master..%s' % branch
def eval_path(path):
"""handles quoted paths."""
if path.startswith('"') and path.endswith('"'):
return core.decode(eval(path))
else:
return core.decode(path)
def renamed_files(start, end, git=git):
difflines = git.diff('%s..%s' % (start, end), M=True,
**_common_diff_opts()).splitlines()
return [eval_path(r[12:].rstrip())
for r in difflines if r.startswith('rename from ')]
def changed_files(start, end, git=git):
zfiles_str = git.diff('%s..%s' % (start, end),
name_only=True, z=True,
**_common_diff_opts()).strip('\0')
return [core.decode(enc) for enc in zfiles_str.split('\0') if enc]
def parse_ls_tree(rev):
"""Return a list of(mode, type, sha1, path) tuples."""
lines = 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
# A regex for matching the output of git(log|rev-list) --pretty=oneline
REV_LIST_REGEX = re.compile('^([0-9a-f]{40}) (.*)$')
def parse_rev_list(raw_revs):
"""Parse `git log --pretty=online` output into (SHA-1, summary) pairs."""
revs = []
for line in map(core.decode, 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 log_helper(all=False, extra_args=None):
"""Return parallel arrays containing the SHA-1s and summaries."""
revs = []
summaries = []
args = []
if extra_args:
args = extra_args
output = git.log(pretty='oneline', no_color=True, all=all, *args)
for line in map(core.decode, output.splitlines()):
match = REV_LIST_REGEX.match(line)
if match:
revs.append(match.group(1))
summaries.append(match.group(2))
return (revs, summaries)
def rev_list_range(start, end):
"""Return a (SHA-1, summary) pairs between start and end."""
revrange = '%s..%s' % (start, end)
raw_revs = git.rev_list(revrange, pretty='oneline')
return parse_rev_list(raw_revs)
def merge_message_path():
"""Return the path to .git/MERGE_MSG or .git/SQUASH_MSG."""
for basename in ('MERGE_MSG', 'SQUASH_MSG'):
path = git.git_path(basename)
if os.path.exists(path):
return path
return None
def abort_merge():
"""Abort a merge by reading the tree at HEAD."""
# Reset the worktree
git.read_tree('HEAD', reset=True, u=True, v=True)
# remove MERGE_HEAD
merge_head = git.git_path('MERGE_HEAD')
if os.path.exists(merge_head):
os.unlink(merge_head)
# remove MERGE_MESSAGE, etc.
merge_msg_path = merge_message_path()
while merge_msg_path:
os.unlink(merge_msg_path)
merge_msg_path = merge_message_path()
def merge_message(revision):
"""Return a merge message for FETCH_HEAD."""
fetch_head = git.git_path('FETCH_HEAD')
if os.path.exists(fetch_head):
return git.fmt_merge_msg('--file', fetch_head)
return "Merge branch '%s'" % revision
+276
View File
@@ -0,0 +1,276 @@
import os
import cola
import cola.app
from cola import core
from cola import git
from cola import gitcmds
from cola import qtutils
from cola import signals
from cola.widgets.browse import BrowseDialog
from cola.widgets.combodlg import ComboDialog
def install_command_wrapper():
wrapper = CommandWrapper()
cola.factory().add_command_wrapper(wrapper)
class CommandWrapper(object):
def __init__(self):
self.callbacks = {
signals.confirm: qtutils.confirm,
signals.critical: qtutils.critical,
signals.information: qtutils.information,
signals.question: qtutils.question,
}
def choose_from_combo(title, items):
"""Quickly choose an item from a list using a combo box"""
return ComboDialog(qtutils.active_window(), title=title, items=items).selected()
def slot_with_parent(fn, parent):
"""Return an argument-less method for calling fn(parent=parent)
:param fn: - Function reference, must accept 'parent' as a keyword
:param parent: - Qt parent widget
"""
def slot():
fn(parent=parent)
return slot
def branch_delete():
"""Launch the 'Delete Branch' dialog."""
branch = choose_from_combo('Delete Branch',
cola.model().local_branches)
if not branch:
return
cola.notifier().broadcast(signals.delete_branch, branch)
def branch_diff():
"""Diff against an arbitrary revision, branch, tag, etc."""
branch = choose_from_combo('Select Branch, Tag, or Commit-ish',
['HEAD^'] +
cola.model().all_branches() +
cola.model().tags)
if not branch:
return
cola.notifier().broadcast(signals.diff_mode, branch)
def browse_current():
"""Launch the 'Browse Current Branch' dialog."""
branch = gitcmds.current_branch()
BrowseDialog.browse(branch)
def browse_other():
"""Prompt for a branch and inspect content at that point in time."""
# Prompt for a branch to browse
branch = choose_from_combo('Browse Revision...', gitcmds.all_refs())
if not branch:
return
BrowseDialog.browse(branch)
def checkout_branch():
"""Launch the 'Checkout Branch' dialog."""
branch = choose_from_combo('Checkout Branch',
cola.model().local_branches)
if not branch:
return
cola.notifier().broadcast(signals.checkout_branch, branch)
def cherry_pick():
"""Launch the 'Cherry-Pick' dialog."""
from cola.controllers.selectcommits import select_commits
revs, summaries = gitcmds.log_helper(all=True)
commits = select_commits('Cherry-Pick Commit',
revs, summaries, multiselect=False)
if not commits:
return
cola.notifier().broadcast(signals.cherry_pick, commits)
def clone_repo(spawn=True):
"""
Present GUI controls for cloning a repository
A new cola session is invoked when 'spawn' is True.
"""
url, ok = qtutils.prompt('Path or URL to clone (Env. $VARS okay)')
url = os.path.expandvars(core.encode(url))
if not ok or not url:
return None
try:
# Pick a suitable basename by parsing the URL
newurl = url.replace('\\', '/')
default = newurl.rsplit('/', 1)[-1]
if default == '.git':
# The end of the URL is /.git, so assume it's a file path
default = os.path.basename(os.path.dirname(newurl))
if default.endswith('.git'):
# The URL points to a bare repo
default = default[:-4]
if url == '.':
# The URL is the current repo
default = os.path.basename(os.getcwd())
if not default:
raise
except:
cola.notifier().broadcast(signals.information,
'Error Cloning',
'Could not parse: "%s"' % url)
qtutils.log(1, 'Oops, could not parse git url: "%s"' % url)
return None
# Prompt the user for a directory to use as the parent directory
msg = 'Select a parent directory for the new clone'
dirname = qtutils.opendir_dialog(msg, cola.model().getcwd())
if not dirname:
return None
count = 1
dirname = core.decode(dirname)
destdir = os.path.join(dirname, core.decode(default))
olddestdir = destdir
if os.path.exists(destdir):
# An existing path can be specified
msg = ('"%s" already exists, cola will create a new directory' %
destdir)
cola.notifier().broadcast(signals.information,
'Directory Exists', msg)
# Make sure the new destdir doesn't exist
while os.path.exists(destdir):
destdir = olddestdir + str(count)
count += 1
cola.notifier().broadcast(signals.clone, core.decode(url), destdir,
spawn=spawn)
return destdir
def export_patches():
"""Run 'git format-patch' on a list of commits."""
from cola.controllers.selectcommits import select_commits
revs, summaries = gitcmds.log_helper()
to_export = select_commits('Export Patches', revs, summaries)
if not to_export:
return
to_export.reverse()
revs.reverse()
cola.notifier().broadcast(signals.format_patch, to_export, revs)
def diff_branch():
"""Launches a diff against a branch."""
branch = choose_from_combo('Select Branch, Tag, or Commit-ish',
['HEAD^'] +
cola.model().all_branches() +
cola.model().tags)
if not branch:
return
zfiles_str = git.instance().diff(branch, name_only=True,
no_color=True,
z=True).rstrip('\0')
files = [core.decode(f) for f in zfiles_str.split('\0')]
filename = BrowseDialog.select_file_from_list(files)
if not filename:
return
cola.notifier().broadcast(signals.branch_mode, branch, filename)
def diff_expression():
"""Diff using an arbitrary expression."""
expr = choose_from_combo('Enter Diff Expression',
cola.model().all_branches() +
cola.model().tags)
if not expr:
return
cola.notifier().broadcast(signals.diff_expr_mode, expr)
def goto_grep(line):
"""Called when Search -> Grep's right-click 'goto' action."""
filename, line_number, contents = line.split(':', 2)
filename = core.encode(filename)
cola.notifier().broadcast(signals.edit, [filename], line_number=line_number)
def grep():
"""Prompt and use 'git grep' to find the content."""
# This should be a command in cola.cmds.
txt, ok = qtutils.prompt('grep')
if not ok:
return
cola.notifier().broadcast(signals.grep, txt)
def open_repo():
"""Spawn a new cola session."""
dirname = qtutils.opendir_dialog('Open Git Repository...',
cola.model().getcwd())
if not dirname:
return
cola.notifier().broadcast(signals.open_repo, dirname)
def load_commitmsg():
"""Load a commit message from a file."""
filename = qtutils.open_dialog('Load Commit Message...',
cola.model().getcwd())
if filename:
cola.notifier().broadcast(signals.load_commit_message, filename)
def rebase():
"""Rebase onto a branch."""
branch = choose_from_combo('Rebase Branch',
cola.model().all_branches())
if not branch:
return
#TODO cmd
status, output = git.instance().rebase(branch,
with_stderr=True,
with_status=True)
qtutils.log(status, output)
def review_branch():
"""Diff against an arbitrary revision, branch, tag, etc."""
branch = choose_from_combo('Select Branch, Tag, or Commit-ish',
cola.model().all_branches() +
cola.model().tags)
if not branch:
return
cola.notifier().broadcast(signals.review_branch_mode, branch)
def fetch():
"""Launch the 'fetch' remote dialog."""
from cola.controllers.remote import remote_action
remote_action('fetch')
def push():
"""Launch the 'push' remote dialog."""
from cola.controllers.remote import remote_action
remote_action('push')
def pull():
"""Launch the 'pull' remote dialog."""
from cola.controllers.remote import remote_action
return remote_action('pull')
+89
View File
@@ -0,0 +1,89 @@
"""i18n and l10n support for git-cola"""
import gettext as _gettext
import os
import sys
from cola import resources
_null_translation = _gettext.NullTranslations()
_translation = _null_translation
def gettext(s):
return _translation.ugettext(s)
def ngettext(s, p, n):
return _translation.ungettext(s, p, n)
def N_(s):
return s
def install(locale):
global _translation
if sys.platform == 'win32':
_check_win32_locale()
if locale:
os.environ['LANG'] = locale
os.environ['LC_MESSAGES'] = locale
_gettext.textdomain('messages')
_translation = _gettext.translation('git-cola',
localedir=_get_locale_dir(),
fallback=True)
def uninstall():
global _translation
_translation = _null_translation
def _get_locale_dir():
return resources.prefix('share', 'locale')
def _check_win32_locale():
for i in ('LANGUAGE','LC_ALL','LC_MESSAGES','LANG'):
if os.environ.get(i):
break
else:
lang = None
import locale
try:
import ctypes
except ImportError:
# use only user's default locale
lang = locale.getdefaultlocale()[0]
else:
# using ctypes to determine all locales
lcid_user = ctypes.windll.kernel32.GetUserDefaultLCID()
lcid_system = ctypes.windll.kernel32.GetSystemDefaultLCID()
if lcid_user != lcid_system:
lcid = [lcid_user, lcid_system]
else:
lcid = [lcid_user]
lang = [locale.windows_locale.get(i) for i in lcid]
lang = ':'.join([i for i in lang if i])
# set lang code for gettext
if lang:
os.environ['LANGUAGE'] = lang
# additional strings for translation
if 0:
# file kinds
N_('file')
N_('directory')
N_('symlink')
# bugs status
N_('fixed')
# qcat titles for various file types
N_('View text file')
N_('View image file')
N_('View binary file')
N_('View symlink')
N_('View directory')
#
N_("No changes selected to commit")
N_("No changes selected to revert")
+256
View File
@@ -0,0 +1,256 @@
# Copyright (c) 2008 David Aguilar
"""Provides an inotify plugin for Linux and other systems with pyinotify"""
import os
from threading import Timer
from threading import Lock
from cola import utils
try:
import pyinotify
from pyinotify import ProcessEvent
from pyinotify import WatchManager
from pyinotify import Notifier
from pyinotify import EventsCodes
AVAILABLE = True
except ImportError:
ProcessEvent = object
AVAILABLE = False
if utils.is_win32():
try:
import win32file
import win32con
import pywintypes
import win32event
AVAILABLE = True
except ImportError:
ProcessEvent = object
AVAILABLE = False
from PyQt4 import QtCore
import cola
from cola import core
from cola import signals
from cola.compat import set
_thread = None
def start():
global _thread
if not AVAILABLE:
if utils.is_win32():
msg = ('file notification: disabled\n'
'Note: install pywin32 to enable.\n')
elif utils.is_linux():
msg = ('inotify: disabled\n'
'Note: install python-pyinotify to enable inotify.\n')
else:
return
if utils.is_debian():
msg += ('On Debian systems '
'try: sudo aptitude install python-pyinotify')
cola.notifier().broadcast(signals.log_cmd, 0, msg)
return
# Start the notification thread
_thread = GitNotifier()
_thread.start()
if utils.is_win32():
msg = 'file notification: enabled'
else:
msg = 'inotify support: enabled'
cola.notifier().broadcast(signals.log_cmd, 0, msg)
def stop():
if not has_inotify():
return
_thread.stop(True)
_thread.wait()
def has_inotify():
"""Return True if pyinotify is available."""
return AVAILABLE and _thread and _thread.isRunning()
class Handler():
"""Queues filesystem events for broadcast"""
def __init__(self):
"""Create an event handler"""
## Timer used to prevent notification floods
self._timer = None
## The files that have changed since the last broadcast
self._files = set()
## Lock to protect files and timer from threading issues
self._lock = Lock()
def broadcast(self):
"""Broadcasts a list of all files touched since last broadcast"""
with self._lock:
cola.notifier().broadcast(signals.update_status, self._files)
self._files = set()
self._timer = None
def handle(self, path):
"""Queues up filesystem events for broadcast"""
with self._lock:
self._files.add(path)
if self._timer is None:
self._timer = Timer(0.333, self.broadcast)
self._timer.start()
class FileSysEvent(ProcessEvent):
"""Generated by GitNotifier in response to inotify events"""
def __init__(self):
"""Maintain event state"""
ProcessEvent.__init__(self)
## Takes care of Queueing events for broadcast
self._handler = Handler()
def process_default(self, event):
"""Queues up inotify events for broadcast"""
if event.name is not None:
path = os.path.join(event.path, event.name)
path = os.path.relpath(path)
self._handler.handle(path)
class GitNotifier(QtCore.QThread):
"""Polls inotify for changes and generates FileSysEvents"""
def __init__(self, timeout=333):
"""Set up the pyinotify thread"""
QtCore.QThread.__init__(self)
## Git command object
self._git = cola.model().git
## pyinotify timeout
self._timeout = timeout
## Path to monitor
self._path = self._git.worktree()
## Signals thread termination
self._running = True
## Directories to watching
self._dirs_seen = set()
## The inotify watch manager instantiated in run()
self._wmgr = None
## Events to capture
if utils.is_linux():
self._mask = (EventsCodes.ALL_FLAGS['IN_ATTRIB'] |
EventsCodes.ALL_FLAGS['IN_CLOSE_WRITE'] |
EventsCodes.ALL_FLAGS['IN_DELETE'] |
EventsCodes.ALL_FLAGS['IN_MODIFY'] |
EventsCodes.ALL_FLAGS['IN_MOVED_TO'])
def stop(self, stopped):
"""Tells the GitNotifier to stop"""
self._timeout = 0
self._running = not stopped
def _watch_directory(self, directory):
"""Set up a directory for monitoring by inotify"""
if not self._wmgr:
return
directory = os.path.realpath(directory)
if not os.path.exists(directory):
return
if directory not in self._dirs_seen:
self._wmgr.add_watch(directory, self._mask)
self._dirs_seen.add(directory)
def _is_pyinotify_08x(self):
"""Is this pyinotify 0.8.x?
The pyinotify API changed between 0.7.x and 0.8.x.
This allows us to maintain backwards compatibility.
"""
if hasattr(pyinotify, '__version__'):
if pyinotify.__version__[:3] == '0.8':
return True
return False
def run(self):
"""Create the inotify WatchManager and generate FileSysEvents"""
if utils.is_win32():
self.run_win32()
return
# Only capture events that git cares about
self._wmgr = WatchManager()
if self._is_pyinotify_08x():
notifier = Notifier(self._wmgr, FileSysEvent(),
timeout=self._timeout)
else:
notifier = Notifier(self._wmgr, FileSysEvent())
self._watch_directory(self._path)
# Register files/directories known to git
for filename in core.decode(self._git.ls_files()).splitlines():
filename = os.path.realpath(filename)
directory = os.path.dirname(filename)
self._watch_directory(directory)
# self._running signals app termination. The timeout is a tradeoff
# between fast notification response and waiting too long to exit.
while self._running:
if self._is_pyinotify_08x():
check = notifier.check_events()
else:
check = notifier.check_events(timeout=self._timeout)
if not self._running:
break
if check:
notifier.read_events()
notifier.process_events()
notifier.stop()
def run_win32(self):
"""Generate notifications using pywin32"""
hDir = win32file.CreateFile(
self._path,
0x0001,
win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE,
None,
win32con.OPEN_EXISTING,
win32con.FILE_FLAG_BACKUP_SEMANTICS |
win32con.FILE_FLAG_OVERLAPPED,
None)
flags = (win32con.FILE_NOTIFY_CHANGE_FILE_NAME |
win32con.FILE_NOTIFY_CHANGE_DIR_NAME |
win32con.FILE_NOTIFY_CHANGE_ATTRIBUTES |
win32con.FILE_NOTIFY_CHANGE_SIZE |
win32con.FILE_NOTIFY_CHANGE_LAST_WRITE |
win32con.FILE_NOTIFY_CHANGE_SECURITY)
buf = win32file.AllocateReadBuffer(8192)
overlapped = pywintypes.OVERLAPPED()
overlapped.hEvent = win32event.CreateEvent(None, 0, 0, None)
handler = Handler()
while self._running:
win32file.ReadDirectoryChangesW(hDir,
buf,
True,
flags,
overlapped)
rc = win32event.WaitForSingleObject(overlapped.hEvent, self._timeout)
if rc == win32event.WAIT_OBJECT_0:
nbytes = win32file.GetOverlappedResult(hDir, overlapped, True)
if nbytes:
results = win32file.FILE_NOTIFY_INFORMATION(buf, nbytes)
for action, path in results:
if not self._running:
break
path = path.replace('\\', '/')
if not path.startswith('.git/') and os.path.isfile(path):
handler.handle(path)
View File
+26
View File
@@ -0,0 +1,26 @@
"""Provides the main application controller."""
from cola.ctrl import Controller
from cola import signals
class MainController(Controller):
def __init__(self, model, view):
Controller.__init__(self, model, view)
self.add_global_command(signals.amend_mode)
self.add_global_command(signals.diffstat)
self.add_global_command(signals.load_commit_message)
self.add_global_command(signals.load_commit_template)
self.add_global_command(signals.load_previous_message)
self.add_global_command(signals.rescan)
self.add_global_command(signals.reset_mode)
self.add_global_command(signals.run_config_action)
self.add_global_command(signals.signoff)
self.add_global_command(signals.stage_untracked)
self.add_global_command(signals.stage_modified)
self.add_global_command(signals.stage_untracked)
self.add_global_command(signals.unstage_all)
self.add_global_command(signals.unstage_selected)
self.add_global_command(signals.visualize_all)
self.add_global_command(signals.visualize_current)
+547
View File
@@ -0,0 +1,547 @@
# Copyright (c) 2008 David Aguilar
"""This module provides the central cola model.
"""
import os
import time
import copy
from cola import core
from cola import utils
from cola import git
from cola import gitcfg
from cola import gitcmds
from cola.compat import set
from cola.obsmodel import ObservableModel
from cola.decorators import memoize
# Static GitConfig instance
_config = gitcfg.instance()
# Provides access to a global MainModel instance
@memoize
def model():
"""Returns the main model singleton"""
return MainModel()
class MainModel(ObservableModel):
"""Provides a friendly wrapper for doing common git operations."""
# Observable messages
message_about_to_update = 'about_to_update'
message_commit_message_changed = 'commit_message_changed'
message_diff_text_changed = 'diff_text_changed'
message_filename_changed = 'filename_changed'
message_head_changed = 'head_changed'
message_mode_changed = 'mode_changed'
message_updated = 'updated'
# States
mode_none = 'none' # Default: nothing's happened, do nothing
mode_worktree = 'worktree' # Comparing index to worktree
mode_index = 'index' # Comparing index to last commit
mode_amend = 'amend' # Amending a commit
mode_grep = 'grep' # We ran Search -> Grep
mode_branch = 'branch' # Applying changes from a branch
mode_diff = 'diff' # Diffing against an arbitrary branch
mode_diff_expr = 'diff_expr' # Diffing using arbitrary expression
mode_review = 'review' # Reviewing a branch
# Modes where we don't do anything like staging, etc.
modes_read_only = (mode_branch, mode_grep,
mode_diff, mode_diff_expr, mode_review)
# Modes where we can checkout files from the $head
modes_undoable = (mode_none, mode_index, mode_worktree)
unstaged = property(lambda self: self.modified + self.unmerged + self.untracked)
"""An aggregate of the modified, unmerged, and untracked file lists."""
def __init__(self, cwd=None):
"""Reads git repository settings and sets several methods
so that they refer to the git module. This object
encapsulates cola's interaction with git."""
ObservableModel.__init__(self)
# Initialize the git command object
self.git = git.instance()
#####################################################
self.head = 'HEAD'
self.diff_text = ''
self.mode = self.mode_none
self.filename = None
self.currentbranch = ''
self.trackedbranch = ''
self.directory = ''
self.git_version = self.git.version()
self.remotes = []
self.local_branch = ''
self.remote_branch = ''
#####################################################
# Status info
self.commitmsg = ''
self.modified = []
self.staged = []
self.untracked = []
self.unmerged = []
self.upstream_changed = []
self.submodules = set()
#####################################################
# Refs
self.revision = ''
self.local_branches = []
self.remote_branches = []
self.tags = []
self.revisions = []
self.summaries = []
self.fetch_helper = None
self.push_helper = None
self.pull_helper = None
self.generate_remote_helpers()
if cwd:
self.set_worktree(cwd)
#####################################################
# Dag
self._commits = []
def read_only(self):
return self.mode in self.modes_read_only
def undoable(self):
"""Whether we can checkout files from the $head."""
return self.mode in self.modes_undoable
def enable_staging(self):
"""Whether staging should be allowed."""
return self.mode == self.mode_worktree
def generate_remote_helpers(self):
"""Generates helper methods for fetch, push and pull"""
self.push_helper = self.gen_remote_helper(self.git.push, push=True)
self.fetch_helper = self.gen_remote_helper(self.git.fetch)
self.pull_helper = self.gen_remote_helper(self.git.pull)
def editor(self):
app = _config.get('gui.editor', 'gvim')
return {'vim': 'gvim'}.get(app, app)
def history_browser(self):
return _config.get('gui.historybrowser', 'gitk')
def all_branches(self):
return (self.local_branches + self.remote_branches)
def set_worktree(self, worktree):
self.git.set_worktree(worktree)
is_valid = self.git.is_valid()
if is_valid:
basename = os.path.basename(self.git.worktree())
self.set_project(core.decode(basename))
return is_valid
def set_commitmsg(self, msg):
self.commitmsg = msg
self.notify_message_observers(self.message_commit_message_changed, msg)
def set_diff_text(self, txt):
self.diff_text = txt
self.notify_message_observers(self.message_diff_text_changed, txt)
def set_filename(self, filename):
self.filename = filename
self.notify_message_observers(self.message_filename_changed, filename)
def set_head(self, head):
self.head = head
self.notify_message_observers(self.message_head_changed, head)
def set_mode(self, mode):
self.mode = mode
self.notify_message_observers(self.message_mode_changed, mode)
def set_remote(self, remote):
if not remote:
return
self.set_param('remote', remote)
branches = utils.grep('%s/\S+$' % remote,
gitcmds.branch_list(remote=True),
squash=False)
self.set_remote_branches(branches)
def apply_diff(self, filename):
return self.git.apply(filename, index=True, cached=True,
with_stderr=True, with_status=True)
def apply_diff_to_worktree(self, filename):
return self.git.apply(filename,
with_stderr=True, with_status=True)
def prev_commitmsg(self, *args):
"""Queries git for the latest commit message."""
log = self.git.log('-1', no_color=True, pretty='format:%s%n%n%b', *args)
return core.decode(log)
def update_file_status(self, update_index=False):
self.notify_message_observers(self.message_about_to_update)
self.notification_enabled = False
self._update_files(update_index=update_index)
self.notification_enabled = True
self.notify_observers('staged', 'unstaged')
self.broadcast_updated()
def update_status(self, update_index=False):
# Give observers a chance to respond
self.notify_message_observers(self.message_about_to_update)
# This allows us to defer notification until the
# we finish processing data
self.notification_enabled = False
self._update_files(update_index=update_index)
self._update_refs()
self._update_branches_and_tags()
self._update_branch_heads()
# Re-enable notifications and emit changes
self.notification_enabled = True
self.notify_observers('staged', 'unstaged')
self.broadcast_updated()
def update_status_of_files(self, files):
self.notify_message_observers(self.message_about_to_update)
self.notification_enabled = False
states = gitcmds.partial_worktree_state_dict(files, self.head)
for status, path in states:
if status == 'unmodified':
if path in self.modified:
self.modified.remove(path)
if path in self.untracked:
self.untracked.remove(path)
elif status == 'modified':
if path in self.untracked:
self.untracked.remove(path)
if path not in self.modified:
self.modified.append(path)
elif status == 'untracked':
if path in self.modified:
self.modified.remove(path)
if path not in self.untracked:
self.untracked.append(path)
self.modified.sort()
self.untracked.sort()
self.notification_enabled = True
self.notify_observers('staged', 'unstaged')
self.broadcast_updated()
def broadcast_updated(self):
self.notify_message_observers(self.message_updated)
def _update_files(self, worktree_only=False, update_index=False):
staged_only = self.read_only()
state = gitcmds.worktree_state_dict(head=self.head,
update_index=update_index,
staged_only=staged_only)
self.staged = state.get('staged', [])
self.modified = state.get('modified', [])
self.unmerged = state.get('unmerged', [])
self.untracked = state.get('untracked', [])
self.submodules = state.get('submodules', set())
self.upstream_changed = state.get('upstream_changed', [])
def _update_refs(self):
self.set_remotes(self.git.remote().splitlines())
self.set_revision('')
self.set_local_branch('')
self.set_remote_branch('')
def _update_branch_heads(self):
# Set these early since they are used to calculate 'upstream_changed'.
self.set_trackedbranch(gitcmds.tracked_branch())
self.set_currentbranch(gitcmds.current_branch())
def _update_branches_and_tags(self):
local_branches, remote_branches, tags = gitcmds.all_refs(split=True)
self.set_local_branches(local_branches)
self.set_remote_branches(remote_branches)
self.set_tags(tags)
def delete_branch(self, branch):
return self.git.branch(branch,
D=True,
with_stderr=True,
with_status=True)
def revision_sha1(self, idx):
return self.revisions[idx]
def _sliced_op(self, input_items, map_fn, size=42):
items = copy.copy(input_items)
full_status = 0
full_output = []
while len(items) > 0:
status, output = map_fn(items[:size])
full_status = full_status or status
full_output.append(output)
items = items[size:]
return (full_status, '\n'.join(full_output))
def _sliced_add(self, input_items, size=42):
lambda_fn = lambda x: self.git.add('--',
v=True,
with_stderr=True,
with_status=True,
*x)
return self._sliced_op(input_items, lambda_fn)
def stage_modified(self):
status, output = self._sliced_add(self.modified)
self.update_file_status()
return (status, output)
def stage_untracked(self):
status, output = self._sliced_add(self.untracked)
self.update_file_status()
return (status, output)
def reset(self, *items):
lambda_fn = lambda x: self.git.reset('--',
with_stderr=True,
with_status=True,
*x)
status, output = self._sliced_op(items, lambda_fn)
self.update_file_status()
return (status, output)
def unstage_all(self):
status, output = self.git.reset(self.head, '--', '.',
with_stderr=True,
with_status=True)
self.update_file_status()
return (status, output)
def stage_all(self):
status, output = self.git.add(v=True,
u=True,
with_stderr=True,
with_status=True)
self.update_file_status()
return (status, output)
def config_set(self, key, value, local=True):
# git config category.key value
strval = unicode(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)
def config_dict(self, local=True):
"""parses the lines from git config --list into a dictionary"""
kwargs = {
'list': True,
'global': not local, # global is a python keyword
}
config_lines = self.git.config(**kwargs).splitlines()
newdict = {}
for line in config_lines:
try:
k, v = line.split('=', 1)
except:
# value-less entry in .gitconfig
continue
v = core.decode(v)
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.tmp_filename()
# Create the commit message file
fh = open(tmpfile, 'w')
core.write_nointr(fh, msg)
fh.close()
# Run 'git commit'
status, out = self.git.commit(F=tmpfile, v=True, amend=amend,
with_status=True,
with_stderr=True)
os.unlink(tmpfile)
return (status, out)
def tmp_dir(self):
# Allow TMPDIR/TMP with a fallback to /tmp
return os.environ.get('TMP', os.environ.get('TMPDIR', '/tmp'))
def tmp_file_pattern(self):
return os.path.join(self.tmp_dir(), '*.git-cola.%s.*' % os.getpid())
def tmp_filename(self, prefix=''):
basename = ((prefix+'.git-cola.%s.%s'
% (os.getpid(), time.time())))
basename = basename.replace('/', '-')
basename = basename.replace('\\', '-')
tmpdir = self.tmp_dir()
return os.path.join(tmpdir, basename)
def remote_url(self, name, action):
if action == 'push':
url = self.git.config('remote.%s.pushurl' % name, get=True)
if url:
return url
return self.git.config('remote.%s.url' % name, get=True)
def remote_args(self, remote,
local_branch='',
remote_branch='',
ffwd=True,
tags=False,
rebase=False,
push=False):
# Swap the branches in push mode (reverse of fetch)
if push:
tmp = local_branch
local_branch = remote_branch
remote_branch = tmp
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)
elif local_branch:
args.append(local_branch)
elif remote_branch:
args.append(remote_branch)
kwargs = {
'verbose': True,
'tags': tags,
'rebase': rebase,
'with_stderr': True,
'with_status': True,
}
return (args, kwargs)
def gen_remote_helper(self, gitaction, push=False):
"""Generates a closure that calls git fetch, push or pull
"""
def remote_helper(remote, **kwargs):
args, kwargs = self.remote_args(remote, push=push, **kwargs)
return gitaction(*args, **kwargs)
return remote_helper
def create_branch(self, name, base, track=False):
"""Create a branch named 'name' from revision 'base'
Pass track=True to create a local tracking branch.
"""
return self.git.branch(name, base, track=track,
with_stderr=True,
with_status=True)
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 = []
status = 0
for rev in revs:
newstatus, out = self.git.cherry_pick(rev,
with_stderr=True,
with_status=True)
if status == 0:
status += newstatus
cherries.append(out)
return (status, '\n'.join(cherries))
def pad(self, pstr, num=22):
topad = num-len(pstr)
if topad > 0:
return pstr + ' '*topad
else:
return pstr
def is_commit_published(self):
head = self.git.rev_parse('HEAD')
return bool(self.git.branch(r=True, contains=head))
def everything(self):
"""Returns a sorted list of all files, including untracked files."""
ls_files = self.git.ls_files(z=True,
cached=True,
others=True,
exclude_standard=True)
return sorted(map(core.decode, [f for f in ls_files.split('\0') if f]))
def stage_paths(self, paths):
"""Stages add/removals to git."""
if not paths:
self.stage_all()
return
add = []
remove = []
for path in set(paths):
if os.path.exists(core.encode(path)):
add.append(path)
else:
remove.append(path)
self.notify_message_observers(self.message_about_to_update)
# `git add -u` doesn't work on untracked files
if add:
self._sliced_add(add)
# If a path doesn't exist then that means it should be removed
# from the index. We use `git add -u` for that.
if remove:
while remove:
self.git.add('--', u=True, with_stderr=True, *remove[:42])
remove = remove[42:]
self._update_files()
self.notify_message_observers(self.message_updated)
def unstage_paths(self, paths):
if not paths:
self.unstage_all()
return
gitcmds.unstage_paths(paths, head=self.head)
self.update_file_status()
def getcwd(self):
"""If we've chosen a directory then use it, otherwise os.getcwd()."""
if self.directory:
return self.directory
return os.getcwd()
+570
View File
@@ -0,0 +1,570 @@
"""This view provides the main git-cola user interface.
"""
import os
from PyQt4 import QtCore
from PyQt4 import QtGui
from PyQt4.QtCore import Qt
from PyQt4.QtCore import SIGNAL
import cola
from cola import core
from cola import gitcmds
from cola import guicmds
from cola import merge
from cola import settings
from cola import signals
from cola import gitcfg
from cola import qtutils
from cola import qtcompat
from cola import qt
from cola import resources
from cola import stash
from cola import utils
from cola import version
from cola.bookmarks import manage_bookmarks
from cola.classic import cola_classic
from cola.classic import classic_widget
from cola.controllers import compare
from cola.controllers import createtag
from cola.controllers import search
from cola.controllers.createbranch import create_new_branch
from cola.dag import git_dag
from cola.prefs import diff_font
from cola.prefs import PreferencesModel
from cola.prefs import preferences
from cola.qt import create_button
from cola.qt import create_dock
from cola.qt import create_menu
from cola.qtutils import add_action
from cola.qtutils import connect_button
from cola.qtutils import emit
from cola.qtutils import log
from cola.qtutils import relay_signal
from cola.qtutils import tr
from cola.views import standard
from cola.widgets import cfgactions
from cola.widgets.about import launch_about_dialog
from cola.widgets.commitmsg import CommitMessageEditor
from cola.widgets.diff import DiffTextEdit
from cola.widgets.status import StatusWidget
class MainView(standard.MainWindow):
def __init__(self, model, parent):
standard.MainWindow.__init__(self, parent)
# Default size; this is thrown out when save/restore is used
self.resize(987, 610)
self.model = model
self.prefs_model = prefs_model = PreferencesModel()
# Internal field used by import/export_state().
# Change this whenever dockwidgets are removed.
self.widget_version = 1
# Keeps track of merge messages we've seen
self.merge_message_hash = ''
self.setAcceptDrops(True)
# Dockwidget options
qtcompat.set_common_dock_options(self)
self.classic_dockable = gitcfg.instance().get('cola.classicdockable')
if self.classic_dockable:
self.classicdockwidget = create_dock('Cola Classic', self)
self.classicwidget = classic_widget(self)
self.classicdockwidget.setWidget(self.classicwidget)
# "Actions" widget
self.actiondockwidget = create_dock('Actions', self)
self.actiondockwidgetcontents = qt.QFlowLayoutWidget(self)
layout = self.actiondockwidgetcontents.layout()
self.stage_button = create_button('Stage', layout)
self.unstage_button = create_button('Unstage', layout)
self.rescan_button = create_button('Rescan', layout)
self.fetch_button = create_button('Fetch...', layout)
self.push_button = create_button('Push...', layout)
self.pull_button = create_button('Pull...', layout)
self.stash_button = create_button('Stash...', layout)
self.alt_button = create_button('Exit Diff Mode', layout)
self.alt_button.hide()
layout.addStretch()
self.actiondockwidget.setWidget(self.actiondockwidgetcontents)
# "Repository Status" widget
self.statusdockwidget = create_dock('Repository Status', self)
self.statusdockwidget.setWidget(StatusWidget(self))
# "Commit Message Editor" widget
self.commitdockwidget = create_dock('Commit Message Editor', self)
self.commitmsgeditor = CommitMessageEditor(model, self)
relay_signal(self, self.commitmsgeditor, SIGNAL(signals.amend_mode))
relay_signal(self, self.commitmsgeditor, SIGNAL(signals.signoff))
relay_signal(self, self.commitmsgeditor,
SIGNAL(signals.load_previous_message))
self.commitdockwidget.setWidget(self.commitmsgeditor)
# "Command Output" widget
logwidget = qtutils.logger()
logwidget.setFont(diff_font())
self.logdockwidget = create_dock('Command Output', self)
self.logdockwidget.setWidget(logwidget)
# "Diff Viewer" widget
self.diffdockwidget = create_dock('Diff Viewer', self)
self.diff_viewer = DiffTextEdit(self.diffdockwidget)
self.diffdockwidget.setWidget(self.diff_viewer)
# All Actions
self.menu_unstage_all = add_action(self,
'Unstage All', emit(self, signals.unstage_all))
self.menu_unstage_all.setIcon(qtutils.icon('remove.svg'))
self.menu_unstage_selected = add_action(self,
'Unstage From Commit', emit(self, signals.unstage_selected))
self.menu_unstage_selected.setIcon(qtutils.icon('remove.svg'))
self.menu_show_diffstat = add_action(self,
'Diffstat', emit(self, signals.diffstat), 'Ctrl+D')
self.menu_stage_modified = add_action(self,
'Stage Changed Files To Commit',
emit(self, signals.stage_modified), 'Alt+A')
self.menu_stage_modified.setIcon(qtutils.icon('add.svg'))
self.menu_stage_untracked = add_action(self,
'Stage All Untracked', emit(self, signals.stage_untracked), 'Alt+U')
self.menu_stage_untracked.setIcon(qtutils.icon('add.svg'))
self.menu_export_patches = add_action(self,
'Export Patches...', guicmds.export_patches, 'Ctrl+E')
self.menu_preferences = add_action(self,
'Preferences', lambda: preferences(model=prefs_model),
QtGui.QKeySequence.Preferences, 'Ctrl+O')
self.menu_rescan = add_action(self,
'Rescan', emit(self, signals.rescan), 'Ctrl+R')
self.menu_rescan.setIcon(qtutils.reload_icon())
self.menu_cherry_pick = add_action(self,
'Cherry-Pick...', guicmds.cherry_pick, 'Ctrl+P')
self.menu_load_commitmsg = add_action(self,
'Load Commit Message...', guicmds.load_commitmsg)
self.menu_quit = add_action(self,
'Quit', self.close, 'Ctrl+Q')
self.menu_manage_bookmarks = add_action(self,
'Bookmarks...', manage_bookmarks)
self.menu_grep = add_action(self,
'Grep', guicmds.grep)
self.menu_merge_local = add_action(self,
'Merge...', merge.local_merge)
self.menu_merge_abort = add_action(self,
'Abort Merge...', merge.abort_merge)
self.menu_fetch = add_action(self,
'Fetch...', guicmds.fetch)
self.menu_push = add_action(self,
'Push...', guicmds.push)
self.menu_pull = add_action(self,
'Pull...', guicmds.pull)
self.menu_open_repo = add_action(self,
'Open...', guicmds.open_repo)
self.menu_open_repo.setIcon(qtutils.open_icon())
self.menu_stash = add_action(self,
'Stash...', stash.stash, 'Alt+Shift+S')
self.menu_diff_branch = add_action(self,
'Apply Changes From Branch...', guicmds.diff_branch)
self.menu_branch_compare = add_action(self,
'Branches...', compare.branch_compare)
self.menu_clone_repo = add_action(self,
'Clone...', guicmds.clone_repo)
self.menu_clone_repo.setIcon(qtutils.git_icon())
self.menu_help_docs = add_action(self,
'Documentation',
lambda: self.model.git.web__browse(resources.html_docs()),
QtGui.QKeySequence.HelpContents)
self.menu_commit_compare = add_action(self,
'Commits...', compare.compare)
self.menu_commit_compare_file = add_action(self,
'Commits Touching File...', compare.compare_file)
self.menu_visualize_current = add_action(self,
'Visualize Current Branch...',
emit(self, signals.visualize_current))
self.menu_visualize_all = add_action(self,
'Visualize All Branches...',
emit(self, signals.visualize_all))
self.menu_search_commits = add_action(self,
'Search...', search.search)
self.menu_browse_branch = add_action(self,
'Browse Current Branch...', guicmds.browse_current)
self.menu_browse_other_branch = add_action(self,
'Browse Other Branch...', guicmds.browse_other)
self.menu_load_commitmsg_template = add_action(self,
'Get Commit Message Template',
emit(self, signals.load_commit_template))
self.menu_help_about = add_action(self,
'About', launch_about_dialog)
self.menu_branch_diff = add_action(self,
'SHA-1...', guicmds.branch_diff)
self.menu_diff_expression = add_action(self,
'Expression...', guicmds.diff_expression)
self.menu_create_tag = add_action(self,
'Create Tag...', createtag.create_tag)
self.menu_create_branch = add_action(self,
'Create...', create_new_branch, 'Ctrl+B')
self.menu_delete_branch = add_action(self,
'Delete...', guicmds.branch_delete)
self.menu_checkout_branch = add_action(self,
'Checkout...', guicmds.checkout_branch, 'Alt+B')
self.menu_rebase_branch = add_action(self,
'Rebase...', guicmds.rebase)
self.menu_branch_review = add_action(self,
'Review...', guicmds.review_branch)
self.menu_classic = add_action(self,
'Cola Classic...', cola_classic)
self.menu_classic.setIcon(qtutils.git_icon())
self.menu_dag = add_action(self,
'DAG...', lambda: git_dag(self.model))
self.menu_dag.setIcon(qtutils.git_icon())
# Create the application menu
self.menubar = QtGui.QMenuBar(self)
# File Menu
self.file_menu = create_menu('&File', self.menubar)
self.file_menu.addAction(self.menu_preferences)
self.file_menu.addSeparator()
self.file_menu.addAction(self.menu_open_repo)
self.file_menu.addAction(self.menu_clone_repo)
self.file_menu.addAction(self.menu_manage_bookmarks)
self.file_menu.addSeparator()
self.file_menu.addAction(self.menu_rescan)
self.file_menu.addSeparator()
self.file_menu.addSeparator()
self.file_menu.addAction(self.menu_load_commitmsg)
self.file_menu.addAction(self.menu_load_commitmsg_template)
self.file_menu.addSeparator()
self.file_menu.addAction(self.menu_quit)
# Add to menubar
self.menubar.addAction(self.file_menu.menuAction())
# Commit Menu
self.commit_menu = create_menu('Co&mmit', self.menubar)
self.commit_menu.setTitle(tr('Commit@@verb'))
self.commit_menu.addAction(self.menu_stage_modified)
self.commit_menu.addAction(self.menu_stage_untracked)
self.commit_menu.addSeparator()
self.commit_menu.addAction(self.menu_unstage_all)
self.commit_menu.addAction(self.menu_unstage_selected)
self.commit_menu.addSeparator()
self.commit_menu.addAction(self.menu_search_commits)
# Add to menubar
self.menubar.addAction(self.commit_menu.menuAction())
# Branch Menu
self.branch_menu = create_menu('B&ranch', self.menubar)
self.branch_menu.addAction(self.menu_branch_review)
self.branch_menu.addSeparator()
self.branch_menu.addAction(self.menu_create_branch)
self.branch_menu.addAction(self.menu_checkout_branch)
self.branch_menu.addAction(self.menu_rebase_branch)
self.branch_menu.addAction(self.menu_delete_branch)
self.branch_menu.addSeparator()
self.branch_menu.addAction(self.menu_browse_branch)
self.branch_menu.addAction(self.menu_browse_other_branch)
self.branch_menu.addSeparator()
self.branch_menu.addAction(self.menu_visualize_current)
self.branch_menu.addAction(self.menu_visualize_all)
self.branch_menu.addSeparator()
self.branch_menu.addAction(self.menu_diff_branch)
# Add to menubar
self.menubar.addAction(self.branch_menu.menuAction())
# Actions menu
self.actions_menu = create_menu('Act&ions', self.menubar)
self.actions_menu.addAction(self.menu_merge_local)
self.actions_menu.addAction(self.menu_stash)
self.actions_menu.addSeparator()
self.actions_menu.addAction(self.menu_fetch)
self.actions_menu.addAction(self.menu_push)
self.actions_menu.addAction(self.menu_pull)
self.actions_menu.addSeparator()
self.actions_menu.addAction(self.menu_create_tag)
self.actions_menu.addSeparator()
self.actions_menu.addAction(self.menu_export_patches)
self.actions_menu.addAction(self.menu_cherry_pick)
self.actions_menu.addSeparator()
self.actions_menu.addAction(self.menu_merge_abort)
self.actions_menu.addAction(self.menu_grep)
# Add to menubar
self.menubar.addAction(self.actions_menu.menuAction())
# Diff Menu
self.diff_menu = create_menu('&Diff', self.menubar)
self.diff_menu.addAction(self.menu_branch_diff)
self.diff_menu.addAction(self.menu_diff_expression)
self.diff_menu.addSeparator()
self.diff_menu.addAction(self.menu_branch_compare)
self.diff_menu.addAction(self.menu_commit_compare)
self.diff_menu.addAction(self.menu_commit_compare_file)
self.diff_menu.addSeparator()
self.diff_menu.addAction(self.menu_show_diffstat)
# Add to menubar
self.menubar.addAction(self.diff_menu.menuAction())
# Tools Menu
self.tools_menu = create_menu('&Tools', self.menubar)
self.tools_menu.addAction(self.menu_classic)
self.tools_menu.addAction(self.menu_dag)
self.tools_menu.addSeparator()
if self.classic_dockable:
self.tools_menu.addAction(self.classicdockwidget.toggleViewAction())
self.tools_menu.addAction(self.diffdockwidget.toggleViewAction())
self.tools_menu.addAction(self.actiondockwidget.toggleViewAction())
self.tools_menu.addAction(self.commitdockwidget.toggleViewAction())
self.tools_menu.addAction(self.statusdockwidget.toggleViewAction())
self.tools_menu.addAction(self.logdockwidget.toggleViewAction())
self.menubar.addAction(self.tools_menu.menuAction())
# Help Menu
self.help_menu = create_menu('&Help', self.menubar)
self.help_menu.addAction(self.menu_help_docs)
self.help_menu.addAction(self.menu_help_about)
# Add to menubar
self.menubar.addAction(self.help_menu.menuAction())
# Set main menu
self.setMenuBar(self.menubar)
# Arrange dock widgets
top = Qt.TopDockWidgetArea
bottom = Qt.BottomDockWidgetArea
self.addDockWidget(top, self.commitdockwidget)
if self.classic_dockable:
self.addDockWidget(top, self.classicdockwidget)
self.addDockWidget(top, self.statusdockwidget)
self.addDockWidget(top, self.actiondockwidget)
self.addDockWidget(bottom, self.logdockwidget)
if self.classic_dockable:
self.tabifyDockWidget(self.classicdockwidget, self.commitdockwidget)
self.tabifyDockWidget(self.logdockwidget, self.diffdockwidget)
# Listen for model notifications
model.add_message_observer(model.message_mode_changed,
self._mode_changed)
model.add_message_observer(model.message_updated,
self._update_view)
prefs_model.add_message_observer(prefs_model.message_config_updated,
self._config_updated)
# Add button callbacks
connect_button(self.rescan_button, emit(self, signals.rescan))
connect_button(self.alt_button, emit(self, signals.reset_mode))
connect_button(self.fetch_button, guicmds.fetch)
connect_button(self.push_button, guicmds.push)
connect_button(self.pull_button, guicmds.pull)
connect_button(self.stash_button, stash.stash)
connect_button(self.stage_button, self.stage)
connect_button(self.unstage_button, self.unstage)
self.connect(self, SIGNAL('update'), self._update_callback)
self.connect(self, SIGNAL('apply_state'), self.apply_state)
self.connect(self, SIGNAL('install_config_actions'),
self._install_config_actions)
# Install .git-config-defined actions
self._config_task = None
self.install_config_actions()
# Restore saved settings
self._gui_state_task = None
self._load_gui_state()
log(0, self.model.git_version + '\ncola version ' + version.version())
# Qt overrides
def closeEvent(self, event):
"""Save state in the settings manager."""
qtutils.save_state(self)
standard.MainWindow.closeEvent(self, event)
# Accessors
mode = property(lambda self: self.model.mode)
def _mode_changed(self, mode):
"""React to mode changes; hide/show the "Exit Diff Mode" button."""
if mode in (self.model.mode_branch,
self.model.mode_review,
self.model.mode_diff,
self.model.mode_diff_expr):
height = self.stage_button.minimumHeight()
self.alt_button.setMinimumHeight(height)
self.alt_button.show()
else:
self.alt_button.setMinimumHeight(1)
self.alt_button.hide()
def _config_updated(self, source, config, value):
if config == 'cola.fontdiff':
font = QtGui.QFont()
if not font.fromString(value):
return
qtutils.logger().setFont(font)
self.diff_viewer.setFont(font)
self.commitmsgeditor.commitmsg.setFont(font)
elif config == 'cola.tabwidth':
# variable-tab-width setting
self.diff_viewer.set_tab_width(value)
def install_config_actions(self):
"""Install .gitconfig-defined actions"""
self._config_task = self._start_config_actions_task()
def _start_config_actions_task(self):
"""Do the expensive "get_config_actions()" call in the background"""
class ConfigActionsTask(QtCore.QRunnable):
def __init__(self, sender):
QtCore.QRunnable.__init__(self)
self._sender = sender
def run(self):
names = cfgactions.get_config_actions()
self._sender.emit(SIGNAL('install_config_actions'), names)
task = ConfigActionsTask(self)
QtCore.QThreadPool.globalInstance().start(task)
return task
def _install_config_actions(self, names):
"""Install .gitconfig-defined actions"""
if not names:
return
menu = self.actions_menu
menu.addSeparator()
for name in names:
menu.addAction(name, emit(self, signals.run_config_action, name))
def _update_view(self):
self.emit(SIGNAL('update'))
def _update_callback(self):
"""Update the title with the current branch and directory name."""
branch = self.model.currentbranch
curdir = core.decode(os.getcwd())
msg = 'Repository: %s\nBranch: %s' % (curdir, branch)
self.commitdockwidget.setToolTip(msg)
title = '%s: %s' % (self.model.project, branch)
if self.mode in (self.model.mode_diff, self.model.mode_diff_expr):
title += ' *** diff mode***'
elif self.mode == self.model.mode_review:
title += ' *** review mode***'
elif self.mode == self.model.mode_amend:
title += ' *** amending ***'
self.setWindowTitle(title)
self.commitmsgeditor.set_mode(self.mode)
if not self.model.read_only() and self.mode != self.model.mode_amend:
# Check if there's a message file in .git/
merge_msg_path = gitcmds.merge_message_path()
if merge_msg_path is None:
return
merge_msg_hash = utils.checksum(core.decode(merge_msg_path))
if merge_msg_hash == self.merge_message_hash:
return
self.merge_message_hash = merge_msg_hash
cola.notifier().broadcast(signals.load_commit_message,
core.decode(merge_msg_path))
def apply_state(self, state):
"""Imports data for save/restore"""
# 1 is the widget version; change when widgets are added/removed
standard.MainWindow.apply_state(self, state)
qtutils.apply_window_state(self, state, 1)
def export_state(self):
"""Exports data for save/restore"""
state = standard.MainWindow.export_state(self)
return qtutils.export_window_state(self, state, self.widget_version)
def _load_gui_state(self):
"""Restores the gui from the preferences file."""
self._gui_state_task = self._start_gui_state_loading_thread()
def _start_gui_state_loading_thread(self):
"""Do expensive file reading and json decoding in the background"""
class LoadGUIStateTask(QtCore.QRunnable):
def __init__(self, sender):
QtCore.QRunnable.__init__(self)
self._sender = sender
def run(self):
state = settings.Settings().get_gui_state(self._sender)
self._sender.emit(SIGNAL('apply_state'), state)
task = LoadGUIStateTask(self)
QtCore.QThreadPool.globalInstance().start(task)
return task
def stage(self):
"""Stage selected files, or all files if no selection exists."""
paths = cola.selection_model().unstaged
if not paths:
cola.notifier().broadcast(signals.stage_modified)
else:
cola.notifier().broadcast(signals.stage, paths)
def unstage(self):
"""Unstage selected files, or all files if no selection exists."""
paths = cola.selection_model().staged
if not paths:
cola.notifier().broadcast(signals.unstage_all)
else:
cola.notifier().broadcast(signals.unstage, paths)
def dragEnterEvent(self, event):
"""Accepts drops"""
standard.MainWindow.dragEnterEvent(self, event)
event.acceptProposedAction()
def dropEvent(self, event):
"""Apply dropped patches with git-am"""
event.accept()
urls = event.mimeData().urls()
if not urls:
return
paths = map(lambda x: unicode(x.path()), urls)
patches = [p for p in paths if p.endswith('.patch')]
dirs = [p for p in paths if os.path.isdir(p)]
dirs.sort()
for d in dirs:
patches.extend(self._gather_patches(d))
# Broadcast the patches to apply
cola.notifier().broadcast(signals.apply_patches, patches)
def _gather_patches(self, path):
"""Find patches in a subdirectory"""
patches = []
for root, subdirs, files in os.walk(path):
for name in [f for f in files if f.endswith('.patch')]:
patches.append(os.path.join(root, name))
return patches
+40
View File
@@ -0,0 +1,40 @@
from PyQt4 import QtGui
from cola import gitcmds
from cola import qtutils
from cola.merge.controller import MergeController
from cola.merge.model import MergeModel
from cola.merge.view import MergeView
def local_merge():
"""Provides a dialog for merging branches"""
model = MergeModel()
view = MergeView(model, qtutils.active_window())
ctl = MergeController(model, view)
view.show()
view.raise_()
return ctl
def abort_merge():
"""Prompts before aborting a merge in progress
"""
title = qtutils.tr('Abort Merge...')
txt = ('Aborting the current merge will cause '
'*ALL* uncommitted changes to be lost.\n'
'Recovering uncommitted changes is not possible.')
info_txt = 'Aborting the current merge?'
ok_txt = 'Abort Merge'
if qtutils.confirm(title, txt, info_txt, ok_txt,
default=False, icon=qtutils.icon('undo.svg')):
gitcmds.abort_merge()
if __name__ == '__main__':
from cola import app
app = app.ColaApplication([])
ctl = local_merge()
ctl.model.update_status()
app.exec_()
+8
View File
@@ -0,0 +1,8 @@
"""Translate signals from the merge dialog."""
from cola.ctrl import Controller
from cola.merge.model import command_directory
class MergeController(Controller):
def __init__(self, model, view):
Controller.__init__(self, model, view)
self.add_commands(command_directory)
+70
View File
@@ -0,0 +1,70 @@
import cola
from cola import gitcmds
from cola import observable
from cola import signals
from cola.cmds import BaseCommand, VisualizeRevision, visualize_revision
class MergeModel(observable.Observable):
message_updated = 'updated'
def __init__(self):
observable.Observable.__init__(self)
self.model = cola.model()
# Relay the "updated" message
msg = self.model.message_updated
self.model.add_message_observer(msg, self.notify_updated)
def notify_updated(self):
self.notify_message_observers(self.message_updated)
def update_status(self):
self.model.update_status()
def current_branch(self):
return self.model.currentbranch
def local_branches(self):
return self.model.local_branches
def remote_branches(self):
return self.model.remote_branches
def tags(self):
return self.model.tags
def merge(self, revision, no_commit, squash):
msg = gitcmds.merge_message(revision)
status, output = self.model.git.merge('-m'+msg,
revision,
no_commit=no_commit,
squash=squash,
with_stderr=True,
with_status=True)
return status, output
# Merge command
merge = 'merge'
class Merge(BaseCommand):
def __init__(self, revision, no_commit, squash):
BaseCommand.__init__(self)
self.revision = revision
self.no_commit = no_commit
self.squash = squash
def do(self):
squash = self.squash
revision = self.revision
no_commit = self.no_commit
status, output = self.context.merge(revision, no_commit, squash)
notifier = cola.notifier()
notifier.broadcast(signals.log_cmd, status, output)
self.context.update_status()
command_directory = {
merge: Merge,
visualize_revision: VisualizeRevision,
}
+189
View File
@@ -0,0 +1,189 @@
from PyQt4 import QtGui
from PyQt4.QtCore import Qt
from PyQt4.QtCore import SIGNAL
from cola import qtutils
from cola.qt import GitRefLineEdit
from cola.qtutils import tr
from cola.widgets import defs
from cola.merge.model import merge, visualize_revision
class MergeView(QtGui.QDialog):
"""Provides a dialog for merging branches."""
def __init__(self, model, parent=None):
QtGui.QDialog.__init__(self, parent)
self.model = model
self.setWindowModality(Qt.WindowModal)
self.resize(700, 400)
# Widgets
self.title_label = QtGui.QLabel()
self.revision_label = QtGui.QLabel()
self.revision_label.setText(tr('Revision To Merge'))
self.revision = GitRefLineEdit()
self.radio_local = QtGui.QRadioButton()
self.radio_local.setText(tr('Local Branch'))
self.radio_local.setChecked(True)
self.radio_remote = QtGui.QRadioButton()
self.radio_remote.setText(tr('Tracking Branch'))
self.radio_tag = QtGui.QRadioButton()
self.radio_tag.setText(tr('Tag'))
self.revisions = QtGui.QListWidget()
self.revisions.setAlternatingRowColors(True)
self.button_viz = QtGui.QPushButton(self)
self.button_viz.setText(tr('Visualize'))
self.checkbox_squash = QtGui.QCheckBox(self)
self.checkbox_squash.setText(tr('Squash'))
self.checkbox_commit = QtGui.QCheckBox(self)
self.checkbox_commit.setText(tr('Commit'))
self.checkbox_commit.setChecked(True)
self.checkbox_commit_state = True
self.button_cancel = QtGui.QPushButton(self)
self.button_cancel.setText(tr('Cancel'))
self.button_merge = QtGui.QPushButton(self)
self.button_merge.setText(tr('Merge'))
# Layouts
self.revlayt = QtGui.QHBoxLayout()
self.revlayt.addWidget(self.revision_label)
self.revlayt.addWidget(self.revision)
self.revlayt.addStretch()
self.revlayt.addWidget(self.title_label)
self.radiolayt = QtGui.QHBoxLayout()
self.radiolayt.addWidget(self.radio_local)
self.radiolayt.addWidget(self.radio_remote)
self.radiolayt.addWidget(self.radio_tag)
self.buttonlayt = QtGui.QHBoxLayout()
self.buttonlayt.addWidget(self.button_viz)
self.buttonlayt.addStretch()
self.buttonlayt.addWidget(self.checkbox_squash)
self.buttonlayt.addWidget(self.checkbox_commit)
self.buttonlayt.addWidget(self.button_cancel)
self.buttonlayt.addWidget(self.button_merge)
self.mainlayt = QtGui.QVBoxLayout()
self.mainlayt.setMargin(defs.margin)
self.mainlayt.setSpacing(defs.spacing)
self.mainlayt.addLayout(self.radiolayt)
self.mainlayt.addWidget(self.revisions)
self.mainlayt.addLayout(self.revlayt)
self.mainlayt.addLayout(self.buttonlayt)
self.setLayout(self.mainlayt)
self.revision.setFocus()
# Signal/slot connections
self.connect(self.button_cancel, SIGNAL('clicked()'),
self.reject)
self.connect(self.checkbox_squash, SIGNAL('clicked()'),
self.toggle_squash)
self.connect(self.revision, SIGNAL('textChanged(QString)'),
self.update_title)
self.connect(self.revisions, SIGNAL('itemSelectionChanged()'),
self.revision_selected)
self.connect(self.radio_local, SIGNAL('clicked()'),
self.update_revisions)
self.connect(self.radio_remote, SIGNAL('clicked()'),
self.update_revisions)
self.connect(self.radio_tag, SIGNAL('clicked()'),
self.update_revisions)
self.connect(self.button_merge, SIGNAL('clicked()'),
self.merge_revision)
self.connect(self.button_viz, SIGNAL('clicked()'),
self.viz_revision)
# Observer messages
model.add_message_observer(model.message_updated, self.update_all)
self.update_all()
def update_all(self):
"""Set the branch name for the window title and label."""
self.update_title()
self.update_revisions()
def update_title(self, dummy_txt=None):
branch = self.model.current_branch()
revision = unicode(self.revision.text())
if revision:
txt = unicode(tr('Merge "%s" into "%s"')) % (revision, branch)
else:
txt = unicode(tr('Merge into "%s"')) % branch
self.title_label.setText(txt)
self.setWindowTitle(txt)
def toggle_squash(self):
"""Toggles the commit checkbox based on the squash checkbox."""
if self.checkbox_squash.isChecked():
self.checkbox_commit_state =\
self.checkbox_commit.checkState()
self.checkbox_commit.setCheckState(Qt.Unchecked)
self.checkbox_commit.setDisabled(True)
else:
self.checkbox_commit.setDisabled(False)
oldstate = self.checkbox_commit_state
self.checkbox_commit.setCheckState(oldstate)
def update_revisions(self):
"""Update the revision list whenever a radio button is clicked"""
self.revisions.clear()
self.revisions.addItems(self.current_revisions())
def revision_selected(self):
"""Update the revision field when a list item is selected"""
revlist = self.current_revisions()
widget = self.revisions
row, selected = qtutils.selected_row(widget)
if selected and row < len(revlist):
revision = revlist[row]
self.revision.setText(revision)
def current_revisions(self):
"""Retrieve candidate items to merge"""
if self.radio_local.isChecked():
return self.model.local_branches()
elif self.radio_remote.isChecked():
return self.model.remote_branches()
elif self.radio_tag.isChecked():
return self.model.tags()
return []
def viz_revision(self):
"""Launch a gitk-like viewer on the selection revision"""
revision = unicode(self.revision.text())
if not revision:
qtutils.information('No Revision Specified',
'You must specify a revision to view')
return
self.emit(SIGNAL(visualize_revision), revision)
def merge_revision(self):
"""Merge the selected revision/branch"""
revision = unicode(self.revision.text())
if not revision:
qtutils.information('No Revision Specified',
'You must specify a revision to merge')
return
do_commit = self.checkbox_commit.isChecked()
squash = self.checkbox_squash.isChecked()
self.emit(SIGNAL(merge), revision, not(do_commit), squash)
self.accept()
View File
+94
View File
@@ -0,0 +1,94 @@
import re
import cola
from cola import gitcmds
from cola.obsmodel import ObservableModel
class BrowserModel(ObservableModel):
def __init__(self, branch=None):
ObservableModel.__init__(self)
self.copy_params(cola.model())
if branch:
self.currentbranch = branch
# These are parallel lists
# ref^{tree}
self.types = []
self.sha1s = []
self.names = []
self.directories = []
self.directory_entries = {}
# parallel lists
self.subtree_types = []
self.subtree_sha1s = []
self.subtree_names = []
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.currentbranch:
return
self.subtree_types = []
self.subtree_sha1s = []
self.subtree_names = []
self.directories = []
self.directory_entries = {}
# Lookup the tree info
tree_info = gitcmds.parse_ls_tree(self.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 subtree_node(self, idx):
"""Return data for the tree item at idx."""
return (self.subtree_types[idx],
self.subtree_sha1s[idx],
self.subtree_names[idx])
+75
View File
@@ -0,0 +1,75 @@
from cola import core
from cola import git
from cola import gitcmds
from cola.obsmodel import ObservableModel
class CompareModel(ObservableModel):
"""Provides custom model data for CompareController."""
def __init__(self):
ObservableModel.__init__(self)
self.git = git.instance()
self.descriptions_start = []
self.descriptions_end = []
self.revisions_start = []
self.revisions_end = []
self.revision_start = ''
self.revision_end = ''
self.compare_files = []
self.num_results = 100
self.show_versions=False
def update_revision_lists(self, filename=None, show_versions=False):
num_results = self.num_results
if filename:
rev_list = self.git.log('--', filename,
max_count=num_results,
no_color=True,
pretty='oneline')
else:
rev_list = self.git.log(max_count=num_results,
no_color=True,
pretty='oneline', all=True)
commit_list = gitcmds.parse_rev_list(rev_list)
commit_list.reverse()
commits = map(lambda x: x[0], commit_list)
descriptions = map(lambda x: core.decode(x[1]), commit_list)
if show_versions:
fancy_descr_list = map(lambda x: self.describe(*x), commit_list)
self.set_descriptions_start(fancy_descr_list)
self.set_descriptions_end(fancy_descr_list)
else:
self.set_descriptions_start(descriptions)
self.set_descriptions_end(descriptions)
self.set_revisions_start(commits)
self.set_revisions_end(commits)
return commits
def describe(self, revid, descr):
version = self.git.describe(revid, tags=True, always=True,
abbrev=4)
return version + ' - ' + descr
class BranchCompareModel(ObservableModel):
"""Provides custom model data for BranchCompareController."""
def __init__(self):
ObservableModel.__init__(self)
self.git = git.instance()
self.remote_branches = gitcmds.branch_list(remote=True)
self.local_branches = gitcmds.branch_list(remote=False)
self.left_combo = ['Local', 'Remote']
self.right_combo = ['Local', 'Remote']
self.left_combo_index = 0
self.right_combo_index = 1
self.left_list = []
self.right_list = []
self.left_list_index = -1
self.right_list_index = -1
self.left_list_selected = False
self.right_list_selected = False
self.diff_files = []
+10
View File
@@ -0,0 +1,10 @@
from cola.main.model import MainModel
class SearchModel(MainModel):
def __init__(self, cwd=None):
MainModel.__init__(self, cwd=cwd)
self.query = ''
self.max_results = 500
self.start_date = ''
self.end_date = ''
self.commit_list = []
+79
View File
@@ -0,0 +1,79 @@
"""Provides a selection model to handle selection."""
from cola.obsmodel import ObservableModel
from cola.decorators import memoize
@memoize
def selection_model():
"""Provides access to a static SelectionModel instance."""
return SelectionModel()
def selection():
"""Return the current selection."""
model = selection_model()
return (model.staged, model.modified, model.unmerged, model.untracked)
def single_selection():
"""Scan across staged, modified, etc. and return a single item."""
staged, modified, unmerged, untracked = selection()
s = None
m = None
um = None
ut = None
if staged:
s = staged[0]
elif modified:
m = modified[0]
elif unmerged:
um = unmerged[0]
elif untracked:
ut = untracked[0]
return s, m, um, ut
def filename():
s, m, um, ut = single_selection()
if s:
return s
if m:
return m
if um:
return um
if ut:
return ut
return None
class SelectionModel(ObservableModel):
"""Provides information about selected file paths."""
# Notification message sent out when selection changes
message_selection_changed = 'selection_changed'
# These properties wrap the individual selection items
# to provide higher-level pseudo-selections.
unstaged = property(lambda self: self.modified +
self.unmerged +
self.untracked)
all = property(lambda self: self.staged +
self.modified +
self.unmerged +
self.untracked)
def __init__(self):
ObservableModel.__init__(self)
self.staged = []
self.modified = []
self.unmerged = []
self.untracked = []
def set_selection(self, staged, modified, unmerged, untracked):
"""Set the new selection."""
self.set_staged(staged)
self.set_modified(modified)
self.set_unmerged(unmerged)
self.set_untracked(untracked)
self.notify_message_observers(self.message_selection_changed)
+12
View File
@@ -0,0 +1,12 @@
"""Simple data container for the CreateTag dialog."""
import cola
from cola.obsmodel import ObservableModel
class TagModel(ObservableModel):
def __init__(self):
ObservableModel.__init__(self)
self.tag_msg = ''
self.tag_name = ''
self.revision = ['HEAD'] + cola.model().all_branches()
self.sign_tag = False
+37
View File
@@ -0,0 +1,37 @@
import os
from cola.compat import set
from cola.decorators import memoize
debug = os.environ.get('COLA_NOTIFIER_DEBUG', False)
@memoize
def notifier():
return Notifier()
class Notifier(object):
"""Object for sending and receiving notification messages"""
def __init__(self):
self.channels = {}
def broadcast(self, signal, *args, **opts):
if debug:
print ('broadcast: %s(%s, %s)' % (signal,
args or '<empty>',
opts or '<empty>'))
self.emit(signal, *args, **opts)
def emit(self, signal, *args, **opts):
subscribers = self.channels.get(signal, None)
if not subscribers:
return
for fxn in subscribers:
fxn(*args, **opts)
def connect(self, signal, callback):
subscribers = self.channels.setdefault(signal, set())
subscribers.add(callback)
+43
View File
@@ -0,0 +1,43 @@
# Copyright (c) 2008 David Aguilar
"""This module provides the Observable class"""
from cola.compat import set
class Observable(object):
"""Handles subject/observer notifications."""
def __init__(self):
self.observers = set()
self.message_observers = {}
self.notification_enabled = True
def add_observer(self, observer):
"""Adds an observer to this model"""
self.observers.add(observer)
def add_message_observer(self, message, observer):
"""Add an observer for a specific message."""
observers = self.message_observers.setdefault(message, set())
observers.add(observer)
def remove_observer(self, observer):
"""Remove an observer."""
if observer in self.observers:
self.observers.remove(observer)
for message, observers in self.message_observers.items():
if observer in observers:
observers.remove(observer)
def notify_observers(self, *param):
"""Notifies observers about attribute changes"""
if not self.notification_enabled:
return
for observer in self.observers:
observer.notify(*param)
def notify_message_observers(self, message, *args, **opts):
"""Pythonic signals and slots."""
if not self.notification_enabled:
return
observers = self.message_observers.get(message, ())
for method in observers:
method(*args, **opts)
+36
View File
@@ -0,0 +1,36 @@
# Copyright (c) 2008 David Aguilar
"""This module provides the Observer design pattern.
"""
class Observer(object):
"""Implements the Observer pattern for a single subject"""
def __init__(self, model):
self.model = model
self.model.add_observer(self)
def __del__(self):
self.model.remove_observer(self)
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.notification_enabled
model.notification_enabled = False # NOTIFY OFF
value = model.param(attr)
self.subject_changed(attr, value)
model.notification_enabled = notify # NOTIFY ON
def subject_changed(self, attr, value):
"""
Updates the observer/view
This must be implemented by concrete observers class.
"""
msg = 'Concrete Observers must override subject_changed().'
raise NotImplementedError(msg)
+16
View File
@@ -0,0 +1,16 @@
from cola.observable import Observable
from cola.basemodel import BaseModel as Model
class ObservableModel(Model, Observable):
"""Combines serialization from Model with the Observer pattern"""
def __init__(self):
Model.__init__(self)
Observable.__init__(self)
def set_param(self, param, value, notify=True):
"""Override Model.set_param() to handle notification"""
Model.set_param(self, param, value)
# Perform notifications
if notify:
self.notify_observers(param)
+15
View File
@@ -0,0 +1,15 @@
from cola.prefs.view import PreferencesView
from cola.prefs.view import diff_font
from cola.prefs.view import tab_width
from cola.prefs.model import PreferencesModel
from cola.prefs.controller import PreferencesController
def preferences(model=None):
if model is None:
model = PreferencesModel()
prefs = PreferencesView(model)
ctl = PreferencesController(model, prefs)
prefs.show()
prefs.raise_()
return ctl
+8
View File
@@ -0,0 +1,8 @@
from cola.ctrl import Controller
from cola.prefs.model import command_directory
class PreferencesController(Controller):
def __init__(self, model, view):
Controller.__init__(self, model, view)
self.add_commands(command_directory)
+50
View File
@@ -0,0 +1,50 @@
from cola import gitcfg
from cola import observable
from cola.cmds import BaseCommand
class PreferencesModel(observable.Observable):
message_set_config = 'set_config'
message_config_updated = 'config_updated'
def __init__(self):
observable.Observable.__init__(self)
self.config = gitcfg.instance()
def set_config(self, source, config, value):
if source == 'repo':
self.config.set_repo(config, value)
else:
self.config.set_user(config, value)
message = self.message_config_updated
self.notify_message_observers(message, source, config, value)
def get_config(self, source, config):
if source == 'repo':
return self.config.get_repo(config)
else:
return self.config.get(config)
class SetConfigCommand(BaseCommand):
def __init__(self, source, config, value):
BaseCommand.__init__(self)
self.undoable = True
self.source = source
self.config = config
self.value = value
self.old_value = None
def do(self):
self.old_value = self.context.get_config(self.source, self.config)
self.context.set_config(self.source, self.config, self.value)
def undo(self):
if self.old_value is None:
return
self.context.set_config(self.source, self.config, self.old_value)
command_directory = {
PreferencesModel.message_set_config: SetConfigCommand,
}
+298
View File
@@ -0,0 +1,298 @@
import os
from PyQt4 import QtCore
from PyQt4 import QtGui
from PyQt4.QtCore import SIGNAL
from cola import qtutils
from cola import gitcfg
from cola.views import standard
from cola.qtutils import relay_signal
from cola.utils import is_darwin
from cola.widgets import defs
class FormWidget(QtGui.QWidget):
def __init__(self, model, parent, source='user'):
QtGui.QWidget.__init__(self, parent)
self.model = model
self.config_to_widget = {}
self.widget_to_config = {}
self.source = source
self.config = gitcfg.instance()
self.defaults = {}
self.setLayout(QtGui.QFormLayout())
def add_row(self, label, widget):
self.layout().addRow(label, widget)
def set_config(self, config_dict):
self.config_to_widget.update(config_dict)
for config, (widget, default) in config_dict.items():
self.widget_to_config[config] = widget
self.defaults[config] = default
self.connect_widget_to_config(widget, config)
def connect_widget_to_config(self, widget, config):
if isinstance(widget, QtGui.QSpinBox):
widget.connect(widget, SIGNAL('valueChanged(int)'),
self._int_config_changed(config))
elif isinstance(widget, QtGui.QCheckBox):
widget.connect(widget, SIGNAL('toggled(bool)'),
self._bool_config_changed(config))
elif isinstance(widget, QtGui.QLineEdit):
widget.connect(widget, SIGNAL('editingFinished()'),
self._text_config_changed(config))
widget.connect(widget, SIGNAL('returnPressed()'),
self._text_config_changed(config))
def _int_config_changed(self, config):
def emitter(value):
self.emit(SIGNAL(self.model.message_set_config),
self.source, config, value)
return emitter
def _bool_config_changed(self, config):
def emitter(value):
self.emit(SIGNAL(self.model.message_set_config),
self.source, config, value)
return emitter
def _text_config_changed(self, config):
def emitter():
value = unicode(self.sender().text())
self.emit(SIGNAL(self.model.message_set_config),
self.source, config, value)
return emitter
def update_from_config(self):
if self.source == 'repo':
getter = self.config.get_repo
elif self.source == 'user':
getter = self.config.get_user
else:
getter = self.config.get
for config, widget in self.widget_to_config.items():
value = getter(config)
if value is None:
value = self.defaults[config]
self.set_widget_value(widget, value)
def set_widget_value(self, widget, value):
widget.blockSignals(True)
if isinstance(widget, QtGui.QSpinBox):
widget.setValue(value)
elif isinstance(widget, QtGui.QLineEdit):
widget.setText(value)
elif isinstance(widget, QtGui.QCheckBox):
widget.setChecked(value)
widget.blockSignals(False)
class RepoFormWidget(FormWidget):
def __init__(self, model, parent, source):
FormWidget.__init__(self, model, parent, source=source)
self.name = QtGui.QLineEdit()
self.email = QtGui.QLineEdit()
self.merge_verbosity = QtGui.QSpinBox()
self.merge_verbosity.setMinimum(0)
self.merge_verbosity.setMaximum(5)
self.merge_verbosity.setProperty('value', QtCore.QVariant(5))
self.diff_context = QtGui.QSpinBox()
self.diff_context.setMinimum(2)
self.diff_context.setMaximum(99)
self.diff_context.setProperty('value', QtCore.QVariant(5))
self.merge_summary = QtGui.QCheckBox()
self.merge_summary.setChecked(True)
self.merge_diffstat = QtGui.QCheckBox()
self.merge_diffstat.setChecked(True)
self.add_row('User Name', self.name)
self.add_row('Email Address', self.email)
self.add_row('Merge Verbosity', self.merge_verbosity)
self.add_row('Number of Diff Context Lines', self.diff_context)
self.add_row('Summarize Merge Commits', self.merge_summary)
self.add_row('Show Diffstat After Merge', self.merge_diffstat)
self.set_config({
'gui.diffcontext': (self.diff_context, 5),
'user.name': (self.name, ''),
'user.email': (self.email, ''),
'merge.diffstat': (self.merge_diffstat, True),
'merge.summary': (self.merge_summary, True),
'merge.verbosity': (self.merge_verbosity, 5),
})
class SettingsFormWidget(FormWidget):
def __init__(self, model, parent):
FormWidget.__init__(self, model, parent)
self.fixed_font = QtGui.QFontComboBox()
self.fixed_font.setFontFilters(QtGui.QFontComboBox.MonospacedFonts)
self.font_size = QtGui.QSpinBox()
self.font_size.setMinimum(8)
self.font_size.setProperty('value', QtCore.QVariant(12))
self._font_str = None
self.tab_width = QtGui.QSpinBox()
self.tab_width.setWrapping(True)
self.tab_width.setMaximum(42)
self.editor = QtGui.QLineEdit()
self.historybrowser = QtGui.QLineEdit()
self.difftool = QtGui.QLineEdit()
self.mergetool = QtGui.QLineEdit()
self.keep_merge_backups = QtGui.QCheckBox()
self.save_gui_settings = QtGui.QCheckBox()
self.add_row('Fixed-Width Font', self.fixed_font)
self.add_row('Font Size', self.font_size)
self.add_row('Tab Width', self.tab_width)
self.add_row('Editor', self.editor)
self.add_row('History Browser', self.historybrowser)
self.add_row('Diff Tool', self.difftool)
self.add_row('Merge Tool', self.mergetool)
self.add_row('Keep *.orig Merge Backups', self.keep_merge_backups)
self.add_row('Save GUI Settings', self.save_gui_settings)
self.set_config({
'cola.savewindowsettings': (self.save_gui_settings, True),
'cola.tabwidth': (self.tab_width, 8),
'diff.tool': (self.difftool, 'xxdiff'),
'gui.editor': (self.editor, os.getenv('VISUAL', 'gvim')),
'gui.historybrowser': (self.historybrowser, 'gitk'),
'merge.keepbackup': (self.keep_merge_backups, True),
'merge.tool': (self.mergetool, 'xxdiff'),
})
self.connect(self.fixed_font, SIGNAL('currentFontChanged(const QFont &)'),
self.current_font_changed)
self.connect(self.font_size, SIGNAL('valueChanged(int)'),
self.font_size_changed)
def update_from_config(self):
FormWidget.update_from_config(self)
self.fixed_font.blockSignals(True)
self.font_size.blockSignals(True)
font = diff_font()
font_size = font.pointSize()
self.fixed_font.setCurrentFont(font)
self.font_size.setValue(font_size)
self.fixed_font.blockSignals(False)
self.font_size.blockSignals(False)
def font_size_changed(self, size):
font = self.fixed_font.currentFont()
font.setPointSize(size)
self.emit(SIGNAL(self.model.message_set_config),
'user', 'cola.fontdiff', unicode(font.toString()))
def current_font_changed(self, font):
self.emit(SIGNAL(self.model.message_set_config),
'user', 'cola.fontdiff', unicode(font.toString()))
class PreferencesView(standard.Dialog):
def __init__(self, model, parent=None):
standard.Dialog.__init__(self, parent=parent)
self.setWindowTitle(self.tr('Preferences'))
self.resize(600, 360)
self._tabbar = QtGui.QTabBar()
self._tabbar.setDrawBase(False)
self._tabbar.addTab('All Repositories')
self._tabbar.addTab('Current Repository')
self._tabbar.addTab('Settings')
self._user_form = RepoFormWidget(model, self, source='user')
self._repo_form = RepoFormWidget(model, self, source='all')
self._options_form = SettingsFormWidget(model, self)
relay_signal(self, self._user_form, SIGNAL(model.message_set_config))
relay_signal(self, self._repo_form, SIGNAL(model.message_set_config))
relay_signal(self, self._options_form, SIGNAL(model.message_set_config))
self._stackedwidget = QtGui.QStackedWidget()
self._stackedwidget.addWidget(self._user_form)
self._stackedwidget.addWidget(self._repo_form)
self._stackedwidget.addWidget(self._options_form)
self.close_button = QtGui.QPushButton(self)
self.close_button.setText(qtutils.tr('Close'))
self.close_button.setIcon(qtutils.close_icon())
self._button_layt = QtGui.QHBoxLayout()
self._button_layt.setMargin(0)
self._button_layt.setSpacing(defs.spacing)
self._button_layt.addStretch()
self._button_layt.addWidget(self.close_button)
self._layt = QtGui.QVBoxLayout()
self._layt.setMargin(defs.margin)
self._layt.setSpacing(defs.spacing)
self._layt.addWidget(self._tabbar)
self._layt.addWidget(self._stackedwidget)
self._layt.addLayout(self._button_layt)
self.setLayout(self._layt)
self.connect(self._tabbar, SIGNAL('currentChanged(int)'),
self._stackedwidget.setCurrentIndex)
self.connect(self._stackedwidget, SIGNAL('currentChanged(int)'),
self.update_widget)
self.connect(self.close_button, SIGNAL('clicked()'), self.accept)
qtutils.add_close_action(self)
self.update_widget(0)
def update_widget(self, idx):
widget = self._stackedwidget.widget(idx)
widget.update_from_config()
def tab_width():
return gitcfg.instance().get('cola.tabwidth', 8)
def diff_font_str():
font_str = gitcfg.instance().get('cola.fontdiff')
if font_str is None:
font = QtGui.QFont()
family = 'Monospace'
if is_darwin():
family = 'Monaco'
font.setFamily(family)
font_str = unicode(font.toString())
return font_str
def diff_font():
font_str = diff_font_str()
font = QtGui.QFont()
font.fromString(font_str)
return font
if __name__ == "__main__":
import sys
from cola.prefs import preferences
app = QtGui.QApplication(sys.argv)
preferences()
sys.exit(app.exec_())
+299
View File
@@ -0,0 +1,299 @@
# Copyright (c) 2008 David Aguilar
"""This module provides the QObserver class which allows for simple
correspondancies between model parameters and Qt widgets.
The QObserver class handles receiving notifications from
model classes and updating Qt widgets accordingly.
Qt signals are also relayed back to the model so that changes
are always available in the model without having to worry about the
different ways to query Qt widgets.
"""
from PyQt4 import QtGui
from PyQt4 import QtCore
from PyQt4.QtCore import SIGNAL
from cola import observer
from cola.compat import set
class QObserver(observer.Observer, QtCore.QObject):
def __init__(self, model, view, *args, **kwargs):
"""Binds a model and Qt view"""
observer.Observer.__init__(self, model)
QtCore.QObject.__init__(self)
self.view = view
self._actions = {}
self._callbacks = {}
self._widget_names = {}
self._model_to_view = {}
self._mapped_params = set()
self._connected = set()
def SLOT(self, *args):
"""Default slot to handle all Qt callbacks.
This method delegates to callbacks from add_signals."""
widget = self.sender()
param = self._widget_names[widget]
if param in self._mapped_params:
model = self.model
if isinstance(widget, QtGui.QTextEdit):
value = unicode(widget.toPlainText())
model.set_param(param, value, notify=False)
elif isinstance(widget, QtGui.QLineEdit):
value = unicode(widget.text())
model.set_param(param, value, notify=False)
elif isinstance(widget, QtGui.QCheckBox):
model.set_param(param, widget.isChecked())
elif isinstance(widget, QtGui.QSpinBox):
model.set_param(param, widget.value())
elif isinstance(widget, QtGui.QFontComboBox):
value = unicode(widget.currentFont().toString())
if model.has_param(param+'_size'):
size = model.param(param+'_size')
props = value.split(',')
props[1] = str(size)
value = ','.join(props)
model.set_param(param, value)
elif isinstance(widget, QtGui.QDateEdit):
fmt = QtCore.Qt.ISODate
value = str(widget.date().toString(fmt))
model.set_param(param, value)
elif isinstance(widget, QtGui.QListWidget):
row = widget.currentRow()
item = widget.item(row)
if item:
selected = item.isSelected()
else:
selected = False
model.set_param(param+'_selected', selected)
model.set_param(param+'_index', row)
if selected and row != -1:
model.set_param(param+'_item',
model.param(param)[row])
else:
model.set_param(param+'_item', '')
elif isinstance(widget, QtGui.QTreeWidget):
item = widget.currentItem()
if item:
selected = item.isSelected()
row = widget.indexOfTopLevelItem(item)
else:
selected = False
row = -1
model.set_param(param+'_selected', selected)
model.set_param(param+'_index', row)
items = model.param(param)
if selected and row != -1 and row < len(items):
model.set_param(param+'_item', items[row])
else:
model.set_param(param+'_item', '')
elif isinstance(widget, QtGui.QComboBox):
idx = widget.currentIndex()
txt = unicode(widget.currentText())
model.set_param(param+'_index', idx)
model.set_param(param+'_item', txt)
else:
print("SLOT(): Unknown widget:", param, widget)
if param in self._callbacks:
self._callbacks[param](*args)
def add_signals(self, signal_str, *objects):
"""Connects object's signal to the QObserver."""
for obj in objects:
self.connect(obj, SIGNAL(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
widget = getattr(self.view, sender)
self._widget_names[widget] = sender
self._autoconnect(widget)
def add_observables(self, *params):
"""This method assumes that widgets and model params
share the same name."""
for param in params:
widget = getattr(self.view, param)
self._model_to_view[param] = widget
self._widget_names[widget] = param
self._mapped_params.add(param)
self._autoconnect(widget)
def _autoconnect(self, widget):
"""Automagically connects Qt widgets to QObserver.SLOT"""
if widget in self._connected:
return
self._connected.add(widget)
if isinstance(widget, QtGui.QTextEdit):
self.add_signals('textChanged()', widget)
elif isinstance(widget, QtGui.QLineEdit):
self.add_signals('textChanged(const QString&)', widget)
elif isinstance(widget, QtGui.QListWidget):
self.add_signals('itemSelectionChanged()', widget)
self.add_signals('itemClicked(QListWidgetItem *)', widget)
doubleclick = str(widget.objectName())+'_doubleclick'
if hasattr(self, doubleclick):
self.connect(widget,
SIGNAL('itemDoubleClicked(QListWidgetItem *)'),
getattr(self, doubleclick))
elif isinstance(widget, QtGui.QTreeWidget):
self.add_signals('itemSelectionChanged()', widget)
self.add_signals('itemClicked(QTreeWidgetItem *, int)', widget)
doubleclick = str(widget.objectName())+'_doubleclick'
if hasattr(self, doubleclick):
self.connect(widget,
SIGNAL('itemDoubleClicked(QTreeWidgetItem *, int)'),
getattr(self, doubleclick))
elif isinstance(widget, QtGui.QAbstractButton):
self.add_signals('clicked()', widget)
elif isinstance(widget, QtGui.QAction):
self.add_signals('triggered()', widget)
elif isinstance(widget, QtGui.QCheckBox):
self.add_signals('stateChanged(int)', widget)
elif isinstance(widget, QtGui.QSpinBox):
self.add_signals('valueChanged(int)', widget)
elif isinstance(widget, QtGui.QFontComboBox):
self.add_signals('currentFontChanged(const QFont&)', widget)
elif isinstance(widget, QtGui.QSplitter):
self.add_signals('splitterMoved(int,int)', widget)
elif isinstance(widget, QtGui.QDateEdit):
self.add_signals('dateChanged(const QDate&)', widget)
elif isinstance(widget, QtGui.QComboBox):
self.add_signals('currentIndexChanged(int)', widget)
self.add_signals('editTextChanged(QString)', 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.
Action are called in response to view changes.(view->model).
"""
for param, callback in kwargs.iteritems():
if type(callback) is list:
self._actions[param] = callback
else:
self._actions[param] = [callback]
def subject_changed(self, param, value):
"""Sends a model param to the view(model->view)"""
if param in self._model_to_view:
# Temporarily disable notification to avoid loop-backs
notify = self.model.notification_enabled
self.model.notification_enabled = False
widget = self._model_to_view[param]
if isinstance(widget, QtGui.QSpinBox):
widget.setValue(value)
elif isinstance(widget, QtGui.QPixmap):
widget.load(value)
elif isinstance(widget, QtGui.QTextEdit):
widget.setPlainText(value)
elif isinstance(widget, QtGui.QLineEdit):
widget.setText(value)
elif isinstance(widget, QtGui.QListWidget):
self.model.set_param(param+'_item', '')
widget.clear()
for i in value:
widget.addItem(i)
if self.model.has_param(param+'_index'):
idx = self.model.param(param+'_index')
if idx != -1 and idx < len(value):
item = widget.item(idx)
widget.setCurrentItem(item)
widget.setItemSelected(item, True)
self.model.set_param(param+'_item', value[idx])
if param in self._callbacks:
self.model.notification_enabled = True
self._callbacks[param]()
self.model.notification_enabled = False
else:
self.model.set_param(param+'_item', '')
elif isinstance(widget, QtGui.QTreeWidget):
self.model.set_param(param+'_item', '')
widget.clear()
for i in value:
item = QtGui.QTreeWidgetItem([i])
count = widget.topLevelItemCount()
item.setData(0, QtCore.Qt.UserRole,
QtCore.QVariant(count))
widget.addTopLevelItem(item)
if self.model.has_param(param+'_index'):
idx = self.model.param(param+'_index')
if idx != -1 and idx < len(value):
item = widget.topLevelItem(idx)
widget.setCurrentItem(item)
widget.setItemSelected(item, True)
val = value[idx]
self.model.set_param(param+'_item', val)
if param in self._callbacks:
self.model.notification_enabled = True
self._callbacks[param]()
self.model.notification_enabled = False
else:
self.model.set_param(param+'_item', '')
elif isinstance(widget, QtGui.QCheckBox):
widget.setChecked(value)
elif isinstance(widget, QtGui.QFontComboBox):
font = QtGui.QFont()
font.fromString(value)
widget.setCurrentFont(font)
elif isinstance(widget, QtGui.QDateEdit):
if not value:
return
fmt = QtCore.Qt.ISODate
date = QtCore.QDate.fromString(value, fmt)
if date:
widget.setDate(date)
elif isinstance(widget, QtGui.QComboBox):
self.model.set_param(param+'_item', '')
widget.clear()
for item in value:
widget.addItem(item)
if self.model.has_param(param+'_index'):
idx = self.model.param(param+'_index')
if idx != -1 and idx < len(value):
widget.setCurrentIndex(idx)
self.model.set_param(param+'_item', value[idx])
else:
print('subject_changed(): Unknown widget:',
str(widget.objectName()), widget, value)
self.model.notification_enabled = notify
if param not in self._actions:
return
widgets = []
if param in self._model_to_view:
widget = 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):
"""Sends a notification message for each known model parameter."""
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)
+654
View File
@@ -0,0 +1,654 @@
import re
import subprocess
from PyQt4 import QtGui
from PyQt4 import QtCore
from PyQt4.QtCore import Qt
from PyQt4.QtCore import SIGNAL
from PyQt4.QtGui import QFont
from PyQt4.QtGui import QSyntaxHighlighter
from PyQt4.QtGui import QTextCharFormat
from PyQt4.QtGui import QColor
import cola
from cola import utils
from cola import qtutils
from cola.compat import set
from cola.qtutils import tr
from cola.widgets import defs
def create_button(text, layout=None, tooltip=None, icon=None):
"""Create a button, set its title, and add it to the parent."""
button = QtGui.QPushButton()
button.setText(tr(text))
if icon:
button.setIcon(icon)
if layout is not None:
layout.addWidget(button)
return button
def create_dock(title, parent):
"""Create a dock widget and set it up accordingly."""
dock = QtGui.QDockWidget(parent)
dock.setWindowTitle(tr(title))
dock.setObjectName(title)
return dock
def create_menu(title, parent):
"""Create a menu and set its title."""
qmenu = QtGui.QMenu(parent)
qmenu.setTitle(tr(title))
return qmenu
def create_toolbutton(parent, text=None, layout=None, tooltip=None, icon=None):
button = QtGui.QToolButton(parent)
button.setAutoRaise(True)
button.setAutoFillBackground(True)
if icon:
button.setIcon(icon)
if text:
button.setText(tr(text))
button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
if tooltip:
button.setToolTip(tr(tooltip))
if layout is not None:
layout.addWidget(button)
return button
class QFlowLayoutWidget(QtGui.QWidget):
_horizontal = QtGui.QBoxLayout.LeftToRight
_vertical = QtGui.QBoxLayout.TopToBottom
def __init__(self, parent):
QtGui.QWidget.__init__(self, parent)
self._direction = self._vertical
self._layout = layout = QtGui.QBoxLayout(self._direction)
layout.setSpacing(defs.spacing)
layout.setMargin(defs.margin)
self.setLayout(layout)
policy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum,
QtGui.QSizePolicy.Minimum)
self.setSizePolicy(policy)
self.setMinimumSize(QtCore.QSize(1, 1))
def resizeEvent(self, event):
size = event.size()
if size.width() * 0.8 < size.height():
dxn = self._vertical
else:
dxn = self._horizontal
if dxn != self._direction:
self._direction = dxn
self.layout().setDirection(dxn)
class QCollapsibleGroupBox(QtGui.QGroupBox):
def __init__(self, parent=None):
QtGui.QGroupBox.__init__(self, parent)
self.setFlat(True)
self.collapsed = False
self.click_pos = None
self.collapse_icon_size = 16
def set_collapsed(self, collapsed):
self.collapsed = collapsed
for widget in self.findChildren(QtGui.QWidget):
widget.setHidden(collapsed)
self.emit(SIGNAL('toggled(bool)'), collapsed)
def mousePressEvent(self, event):
if event.button() == QtCore.Qt.LeftButton:
option = QtGui.QStyleOptionGroupBox()
self.initStyleOption(option)
icon_size = self.collapse_icon_size
button_area = QtCore.QRect(0, 0, icon_size, icon_size)
top_left = option.rect.adjusted(0, 0, -10, 0).topLeft()
button_area.moveTopLeft(QtCore.QPoint(top_left))
self.click_pos = event.pos()
QtGui.QGroupBox.mousePressEvent(self, event)
def mouseReleaseEvent(self, event):
if (event.button() == QtCore.Qt.LeftButton and
self.click_pos == event.pos()):
self.set_collapsed(not self.collapsed)
QtGui.QGroupBox.mouseReleaseEvent(self, event)
def paintEvent(self, event):
painter = QtGui.QStylePainter(self)
option = QtGui.QStyleOptionGroupBox()
self.initStyleOption(option)
painter.save()
painter.translate(self.collapse_icon_size + 4, 0)
painter.drawComplexControl(QtGui.QStyle.CC_GroupBox, option)
painter.restore()
style = QtGui.QStyle
point = option.rect.adjusted(0, 0, -10, 0).topLeft()
icon_size = self.collapse_icon_size
option.rect = QtCore.QRect(point.x(), point.y(), icon_size, icon_size)
if self.collapsed:
painter.drawPrimitive(style.PE_IndicatorArrowRight, option)
else:
painter.drawPrimitive(style.PE_IndicatorArrowDown, option)
class GitRefCompleter(QtGui.QCompleter):
"""Provides completion for branches and tags"""
def __init__(self, parent):
QtGui.QCompleter.__init__(self, parent)
self._model = GitRefModel(parent)
self.setModel(self._model)
self.setCompletionMode(self.UnfilteredPopupCompletion)
self.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
def __del__(self):
self.dispose()
def dispose(self):
self._model.dispose()
class GitRefLineEdit(QtGui.QLineEdit):
def __init__(self, parent=None):
QtGui.QLineEdit.__init__(self, parent)
self.refcompleter = GitRefCompleter(self)
self.setCompleter(self.refcompleter)
class GitRefModel(QtGui.QStandardItemModel):
def __init__(self, parent):
QtGui.QStandardItemModel.__init__(self, parent)
self.cmodel = cola.model()
msg = self.cmodel.message_updated
self.cmodel.add_message_observer(msg, self.update_matches)
self.update_matches()
def dispose(self):
self.cmodel.remove_observer(self.update_matches)
def update_matches(self):
model = self.cmodel
matches = model.local_branches + model.remote_branches + model.tags
QStandardItem = QtGui.QStandardItem
self.clear()
for match in matches:
item = QStandardItem()
item.setIcon(qtutils.git_icon())
item.setText(match)
self.appendRow(item)
class GitLogCompletionModel(QtGui.QStandardItemModel):
def __init__(self, parent):
self.matched_text = None
QtGui.QStandardItemModel.__init__(self, parent)
self.cmodel = cola.model()
def lower_cmp(self, a, b):
return cmp(a.replace('.','').lower(), b.replace('.','').lower())
def update_matches(self, case_sensitive):
QStandardItem = QtGui.QStandardItem
file_list = self.cmodel.everything()
files = set(file_list)
files_and_dirs = utils.add_parents(set(files))
dirs = files_and_dirs.difference(files)
file_icon = qtutils.file_icon()
dir_icon = qtutils.dir_icon()
git_icon = qtutils.git_icon()
model = self.cmodel
refs = model.local_branches + model.remote_branches + model.tags
matched_text = self.matched_text
if matched_text:
if case_sensitive:
matched_refs = [r for r in refs if matched_text in r]
else:
matched_refs = [r for r in refs
if matched_text.lower() in r.lower()]
else:
matched_refs = refs
matched_refs.sort(cmp=self.lower_cmp)
if matched_text:
if case_sensitive:
matched_paths = [f for f in files_and_dirs
if matched_text in f]
else:
matched_paths = [f for f in files_and_dirs
if matched_text.lower() in f.lower()]
else:
matched_paths = list(files_and_dirs)
matched_paths.sort(cmp=self.lower_cmp)
items = []
for ref in matched_refs:
item = QStandardItem()
item.setText(ref)
item.setIcon(git_icon)
items.append(item)
if matched_paths and (not matched_text or matched_text in '--'):
item = QStandardItem()
item.setText('--')
item.setIcon(file_icon)
items.append(item)
for match in matched_paths:
item = QStandardItem()
item.setText(match)
if match in dirs:
item.setIcon(dir_icon)
else:
item.setIcon(file_icon)
items.append(item)
self.clear()
for item in items:
self.appendRow(item)
def set_match_text(self, text, case_sensitive):
self.matched_text = text
self.update_matches(case_sensitive)
class GitLogLineEdit(QtGui.QLineEdit):
def __init__(self, parent=None):
QtGui.QLineEdit.__init__(self, parent)
# used to hide the completion popup after a drag-select
self._drag = 0
self._model = GitLogCompletionModel(self)
self._delegate = HighlightCompletionDelegate(self)
self._completer = QtGui.QCompleter(self)
self._completer.setWidget(self)
self._completer.setModel(self._model)
self._completer.setCompletionMode(
QtGui.QCompleter.UnfilteredPopupCompletion)
self._completer.popup().setItemDelegate(self._delegate)
self.connect(self._completer, SIGNAL('activated(QString)'),
self._complete)
self.connect(self, SIGNAL('textChanged(QString)'), self._text_changed)
self._keys_to_ignore = set([QtCore.Qt.Key_Enter,
QtCore.Qt.Key_Return,
QtCore.Qt.Key_Escape])
def is_case_sensitive(self, text):
return bool([char for char in text if char.isupper()])
def _text_changed(self, text):
text = self.last_word()
case_sensitive = self.is_case_sensitive(text)
if case_sensitive:
self._completer.setCaseSensitivity(QtCore.Qt.CaseSensitive)
else:
self._completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
self._delegate.set_highlight_text(text, case_sensitive)
self._model.set_match_text(text, case_sensitive)
def update_matches(self):
text = self.last_word()
case_sensitive = self.is_case_sensitive(text)
self._model.update_matches(case_sensitive)
def _complete(self, completion):
"""
This is the event handler for the QCompleter.activated(QString) signal,
it is called when the user selects an item in the completer popup.
"""
if not completion:
return
words = self.words()
if words:
words.pop()
words.append(unicode(completion))
self.setText(subprocess.list2cmdline(words))
self.emit(SIGNAL('ref_changed'))
def words(self):
return utils.shell_usplit(unicode(self.text()))
def last_word(self):
words = self.words()
if not words:
return unicode(self.text())
if not words[-1]:
return u''
return words[-1]
def event(self, event):
if event.type() == QtCore.QEvent.KeyPress:
if (event.key() == QtCore.Qt.Key_Tab and
self._completer.popup().isVisible()):
event.ignore()
return True
return QtGui.QLineEdit.event(self, event)
def do_completion(self):
self._completer.popup().setCurrentIndex(
self._completer.completionModel().index(0,0))
self._completer.complete()
def keyPressEvent(self, event):
if self._completer.popup().isVisible():
if event.key() in self._keys_to_ignore:
event.ignore()
self._complete(self.last_word())
return
elif (event.key() == QtCore.Qt.Key_Down and
self._completer.completionCount() > 0):
event.accept()
self.do_completion()
return
QtGui.QLineEdit.keyPressEvent(self, event)
prefix = self.last_word()
if prefix != unicode(self._completer.completionPrefix()):
self._update_popup_items(prefix)
if len(event.text()) > 0 and len(prefix) > 0:
self._completer.complete()
if len(prefix) == 0:
self._completer.popup().hide()
#: _drag: 0 - unclicked, 1 - clicked, 2 - dragged
def mousePressEvent(self, event):
self._drag = 1
return QtGui.QLineEdit.mousePressEvent(self, event)
def mouseMoveEvent(self, event):
if self._drag == 1:
self._drag = 2
return QtGui.QLineEdit.mouseMoveEvent(self, event)
def mouseReleaseEvent(self, event):
if self._drag != 2 and event.buttons() != QtCore.Qt.RightButton:
self.do_completion()
self._drag = 0
return QtGui.QLineEdit.mouseReleaseEvent(self, event)
def close_popup(self):
self._completer.popup().close()
def _update_popup_items(self, prefix):
"""
Filters the completer's popup items to only show items
with the given prefix.
"""
self._completer.setCompletionPrefix(prefix)
self._completer.popup().setCurrentIndex(
self._completer.completionModel().index(0,0))
class HighlightCompletionDelegate(QtGui.QStyledItemDelegate):
"""A delegate used for auto-completion to give formatted completion"""
def __init__(self, parent=None): # model, parent=None):
QtGui.QStyledItemDelegate.__init__(self, parent)
self.highlight_text = ''
self.case_sensitive = False
self.doc = QtGui.QTextDocument()
self.doc.setDocumentMargin(0)
def set_highlight_text(self, text, case_sensitive):
"""Sets the text that will be made bold in the term name when displayed"""
self.highlight_text = text
self.case_sensitive = case_sensitive
def paint(self, painter, option, index):
"""Overloaded Qt method for custom painting of a model index"""
if not self.highlight_text:
return QtGui.QStyledItemDelegate.paint(self, painter, option, index)
text = unicode(index.data().toPyObject())
if self.case_sensitive:
html = text.replace(self.highlight_text,
'<strong>%s</strong>' % self.highlight_text)
else:
match = re.match('(.*)(' + self.highlight_text + ')(.*)',
text, re.IGNORECASE)
if match:
start = match.group(1) or ''
middle = match.group(2) or ''
end = match.group(3) or ''
html = (start + ('<strong>%s</strong>' % middle) + end)
else:
html = text
self.doc.setHtml(html)
# Painting item without text, Text Document will paint the text
optionV4 = QtGui.QStyleOptionViewItemV4(option)
self.initStyleOption(optionV4, index)
optionV4.text = QtCore.QString()
style = QtGui.QApplication.style()
style.drawControl(QtGui.QStyle.CE_ItemViewItem, optionV4, painter)
ctx = QtGui.QAbstractTextDocumentLayout.PaintContext()
# Highlighting text if item is selected
if (optionV4.state & QtGui.QStyle.State_Selected):
ctx.palette.setColor(QtGui.QPalette.Text, optionV4.palette.color(QtGui.QPalette.Active, QtGui.QPalette.HighlightedText))
# translate the painter to where the text is drawn
textRect = style.subElementRect(QtGui.QStyle.SE_ItemViewItemText, optionV4)
painter.save()
start = textRect.topLeft() + QtCore.QPoint(3, 0)
painter.translate(start)
painter.setClipRect(textRect.translated(-start))
# tell the text document to draw the html for us
self.doc.documentLayout().draw(painter, ctx)
painter.restore()
# Syntax highlighting
def TERMINAL(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 '__TERMINAL__:%s' % pattern
# Cache the results of re.compile so that we don't keep
# rebuilding the same regexes whenever stylesheets change
_RGX_CACHE = {}
def color(c, a=255):
qc = QColor(c)
qc.setAlpha(a)
return qc
default_colors = {
'color_add': color(Qt.green, 128),
'color_remove': color(Qt.red, 128),
'color_begin': color(Qt.darkCyan),
'color_header': color(Qt.darkYellow),
'color_stat_add': color(QColor(32, 255, 32)),
'color_stat_info': color(QColor(32, 32, 255)),
'color_stat_remove': color(QColor(255, 32, 32)),
'color_emphasis': color(Qt.black),
'color_info': color(Qt.blue),
'color_date': color(Qt.darkCyan),
}
class GenericSyntaxHighligher(QSyntaxHighlighter):
def __init__(self, doc, *args, **kwargs):
QSyntaxHighlighter.__init__(self, doc)
for attr, val in default_colors.items():
setattr(self, attr, val)
self._rules = []
self.generate_rules()
def generate_rules(self):
pass
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(TERMINAL(''))
if terminal:
rule = rule[len(TERMINAL('')):]
try:
regex = _RGX_CACHE[rule]
except KeyError:
regex = _RGX_CACHE[rule] = re.compile(rule)
self._rules.append((regex, formats, terminal,))
def formats(self, line):
matched = []
for regex, fmts, terminal in self._rules:
match = regex.match(line)
if not match:
continue
matched.append([match, fmts])
if terminal:
return matched
return matched
def mkformat(self, fg=None, bg=None, bold=False):
fmt = QTextCharFormat()
if fg:
fmt.setForeground(fg)
if bg:
fmt.setBackground(bg)
if bold:
fmt.setFontWeight(QFont.Bold)
return fmt
def highlightBlock(self, qstr):
ascii = unicode(qstr)
if not ascii:
return
formats = self.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
def set_colors(self, colordict):
for attr, val in colordict.items():
setattr(self, attr, val)
class DiffSyntaxHighlighter(GenericSyntaxHighligher):
"""Implements the diff syntax highlighting
This class is used by widgets that display diffs.
"""
def __init__(self, doc, whitespace=True):
self.whitespace = whitespace
GenericSyntaxHighligher.__init__(self, doc)
def generate_rules(self):
diff_begin = self.mkformat(self.color_begin, bold=True)
diff_head = self.mkformat(self.color_header)
diff_add = self.mkformat(bg=self.color_add)
diff_remove = self.mkformat(bg=self.color_remove)
diffstat_info = self.mkformat(self.color_stat_info, bold=True)
diffstat_add = self.mkformat(self.color_stat_add, bold=True)
diffstat_remove = self.mkformat(self.color_stat_remove, bold=True)
if self.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 = TERMINAL('^@@|^\+\+\+|^---')
diff_hd1_rgx = TERMINAL('^diff --git')
diff_hd2_rgx = TERMINAL('^index \S+\.\.\S+')
diff_hd3_rgx = TERMINAL('^new file mode')
diff_add_rgx = TERMINAL('^\+')
diff_rmv_rgx = TERMINAL('^-')
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_info,
None, diffstat_add,
diffstat_remove),
diff_sum_rgx, (diffstat_info,
diffstat_add,
diffstat_remove))
if self.whitespace:
self.create_rules('(..*?)(\s+)$', (None, bad_ws))
if __name__ == '__main__':
import sys
class SyntaxTestDialog(QtGui.QDialog):
def __init__(self, parent):
QtGui.QDialog.__init__(self, parent)
self.resize(720, 512)
self.vboxlayout = QtGui.QVBoxLayout(self)
self.vboxlayout.setObjectName('vboxlayout')
self.output_text = QtGui.QTextEdit(self)
font = QtGui.QFont()
if utils.is_darwin():
family = 'Monaco'
else:
family = 'Monospace'
font.setFamily(family)
font.setPointSize(13)
self.output_text.setFont(font)
self.output_text.setAcceptDrops(False)
self.vboxlayout.addWidget(self.output_text)
self.syntax = DiffSyntaxHighlighter(self.output_text.document())
app = QtGui.QApplication(sys.argv)
dialog = SyntaxTestDialog(qtutils.active_window())
dialog.show()
dialog.raise_()
app.exec_()
+25
View File
@@ -0,0 +1,25 @@
from PyQt4 import QtGui
from PyQt4 import QtCore
def install():
if not hasattr(QtGui.QHBoxLayout, 'setContentsMargins'):
QtGui.QHBoxLayout.setContentsMargins = lambda *args: True
if not hasattr(QtGui.QVBoxLayout, 'setContentsMargins'):
QtGui.QVBoxLayout.setContentsMargins = lambda *args: True
if not hasattr(QtGui.QKeySequence, 'Preferences'):
QtGui.QKeySequence.Preferences = 'Ctrl+O'
def add_search_path(prefix, path):
if hasattr(QtCore.QDir, 'addSearchPath'):
QtCore.QDir.addSearchPath(prefix, path)
def set_common_dock_options(window):
if not hasattr(window, 'setDockOptions'):
return
nested = QtGui.QMainWindow.AllowNestedDocks
tabbed = QtGui.QMainWindow.AllowTabbedDocks
animated = QtGui.QMainWindow.AnimatedDocks
window.setDockOptions(nested | tabbed | animated)
+503
View File
@@ -0,0 +1,503 @@
# Copyright (c) 2008 David Aguilar
"""This module provides miscellaneous Qt utility functions.
"""
import os
from PyQt4 import QtGui
from PyQt4 import QtCore
from PyQt4.QtCore import Qt
from PyQt4.QtCore import SIGNAL
import cola
from cola import core
from cola import gitcfg
from cola import utils
from cola import settings
from cola import signals
from cola import resources
from cola.compat import set
from cola.decorators import memoize
from cola.widgets.log import LogView
@memoize
def logger():
logview = LogView()
cola.notifier().connect(signals.log_cmd, logview.log)
return logview
def log(status, output):
"""Sends messages to the log window.
"""
if not output:
return
cola.notifier().broadcast(signals.log_cmd, status, output)
def emit(widget, signal, *args, **opts):
"""Return a function that emits a signal"""
def emitter(*local_args, **local_opts):
if args or opts:
widget.emit(SIGNAL(signal), *args, **opts)
else:
widget.emit(SIGNAL(signal), *local_args, **local_opts)
return emitter
def SLOT(signal, *args, **opts):
"""
Returns a callback that broadcasts a message over the notifier.
If the caller of SLOT() provides args or opts then those are
used instead of the ones provided by the invoker of the callback.
"""
def broadcast(*local_args, **local_opts):
if args or opts:
cola.notifier().broadcast(signal, *args, **opts)
else:
cola.notifier().broadcast(signal, *local_args, **local_opts)
return broadcast
def connect_button(button, callback):
button.connect(button, SIGNAL('clicked()'), callback)
def relay_button(button, signal):
connect_button(button, SLOT(signal))
def relay_signal(parent, child, signal):
"""Relay a signal from the child widget through the parent"""
def relay_slot(*args, **opts):
parent.emit(signal, *args, **opts)
parent.connect(child, signal, relay_slot)
return relay_slot
def active_window():
return QtGui.QApplication.activeWindow()
def prompt(msg, title=None, text=''):
"""Presents the user with an input widget and returns the input."""
if title is None:
title = msg
msg = tr(msg)
title = tr(title)
result = QtGui.QInputDialog.getText(active_window(), msg, title, text=text)
return (unicode(result[0]), result[1])
def create_listwidget_item(text, filename):
"""Creates a QListWidgetItem with text and the icon at filename."""
item = QtGui.QListWidgetItem()
item.setIcon(QtGui.QIcon(filename))
item.setText(text)
return item
def create_treewidget_item(text, filename):
"""Creates a QTreeWidgetItem with text and the icon at filename."""
icon = cached_icon_from_path(filename)
item = QtGui.QTreeWidgetItem()
item.setIcon(0, icon)
item.setText(0, text)
return item
@memoize
def cached_icon_from_path(filename):
return QtGui.QIcon(filename)
def confirm(title, text, informative_text, ok_text,
icon=None, default=True):
"""Confirm that an action should take place"""
if icon is None:
icon = ok_icon()
elif icon and isinstance(icon, basestring):
icon = QtGui.QIcon(icon)
msgbox = QtGui.QMessageBox(active_window())
msgbox.setWindowTitle(tr(title))
msgbox.setText(tr(text))
msgbox.setInformativeText(tr(informative_text))
ok = msgbox.addButton(tr(ok_text), QtGui.QMessageBox.ActionRole)
ok.setIcon(icon)
cancel = msgbox.addButton(QtGui.QMessageBox.Cancel)
if default:
msgbox.setDefaultButton(ok)
else:
msgbox.setDefaultButton(cancel)
msgbox.exec_()
return msgbox.clickedButton() == ok
def critical(title, message=None, details=None):
"""Show a warning with the provided title and message."""
if message is None:
message = title
title = tr(title)
message = tr(message)
mbox = QtGui.QMessageBox(active_window())
mbox.setWindowTitle(title)
mbox.setTextFormat(QtCore.Qt.PlainText)
mbox.setText(message)
mbox.setIcon(QtGui.QMessageBox.Critical)
mbox.setStandardButtons(QtGui.QMessageBox.Close)
mbox.setDefaultButton(QtGui.QMessageBox.Close)
if details:
mbox.setDetailedText(details)
mbox.exec_()
def information(title, message=None, details=None, informative_text=None):
"""Show information with the provided title and message."""
if message is None:
message = title
title = tr(title)
message = tr(message)
mbox = QtGui.QMessageBox(active_window())
mbox.setStandardButtons(QtGui.QMessageBox.Close)
mbox.setDefaultButton(QtGui.QMessageBox.Close)
mbox.setWindowTitle(title)
mbox.setWindowModality(QtCore.Qt.WindowModal)
mbox.setTextFormat(QtCore.Qt.PlainText)
mbox.setText(message)
if informative_text:
mbox.setInformativeText(tr(informative_text))
if details:
mbox.setDetailedText(details)
# Render git.svg into a 1-inch wide pixmap
pixmap = QtGui.QPixmap(resources.icon('git.svg'))
xres = pixmap.physicalDpiX()
pixmap = pixmap.scaledToHeight(xres, QtCore.Qt.SmoothTransformation)
mbox.setIconPixmap(pixmap)
mbox.exec_()
def question(title, message, default=True):
"""Launches a QMessageBox question with the provided title and message.
Passing "default=False" will make "No" the default choice."""
yes = QtGui.QMessageBox.Yes
no = QtGui.QMessageBox.No
buttons = yes | no
if default:
default = yes
else:
default = no
title = tr(title)
msg = tr(message)
result = (QtGui.QMessageBox
.question(active_window(), title, msg, buttons, default))
return result == QtGui.QMessageBox.Yes
def register_for_signals():
# Register globally with the notifier
notifier = cola.notifier()
notifier.connect(signals.confirm, confirm)
notifier.connect(signals.critical, critical)
notifier.connect(signals.information, information)
notifier.connect(signals.question, question)
register_for_signals()
def selected_treeitem(tree_widget):
"""Returns a(id_number, is_selected) for a QTreeWidget."""
id_number = None
selected = False
item = tree_widget.currentItem()
if item:
id_number = item.data(0, QtCore.Qt.UserRole).toInt()[0]
selected = True
return(id_number, selected)
def selected_row(list_widget):
"""Returns a(row_number, is_selected) tuple for a QListWidget."""
items = list_widget.selectedItems()
if not items:
return (-1, False)
item = items[0]
return (list_widget.row(item), True)
def 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 tree_selection(treeitem, items):
"""Returns model items that correspond to selected widget indices"""
itemcount = treeitem.childCount()
widgetitems = [ treeitem.child(idx) for idx in range(itemcount) ]
selected = []
for item, widgetitem in zip(items[:len(widgetitems)], widgetitems):
if widgetitem.isSelected():
selected.append(item)
return selected
def selected_item(list_widget, items):
"""Returns the selected item in a QListWidget."""
widget_items = list_widget.selectedItems()
if not widget_items:
return None
widget_item = widget_items[0]
row = list_widget.row(widget_item)
if row < len(items):
return items[row]
else:
return None
def open_dialog(title, filename=None):
"""Creates an Open File dialog and returns a filename."""
title_tr = tr(title)
return unicode(QtGui.QFileDialog
.getOpenFileName(active_window(), title_tr, filename))
def opendir_dialog(title, path):
"""Prompts for a directory path"""
flags = (QtGui.QFileDialog.ShowDirsOnly |
QtGui.QFileDialog.DontResolveSymlinks)
title_tr = tr(title)
return unicode(QtGui.QFileDialog
.getExistingDirectory(active_window(),
title_tr, path, flags))
def save_as(filename, title='Save As...'):
"""Creates a Save File dialog and returns a filename."""
title_tr = tr(title)
return unicode(QtGui.QFileDialog
.getSaveFileName(active_window(), title_tr, filename))
def icon(basename):
"""Given a basename returns a QIcon from the corresponding cola icon."""
return QtGui.QIcon(resources.icon(basename))
def set_clipboard(text):
"""Sets the copy/paste buffer to text."""
if not text:
return
clipboard = QtGui.QApplication.instance().clipboard()
clipboard.setText(text, QtGui.QClipboard.Clipboard)
clipboard.setText(text, QtGui.QClipboard.Selection)
def add_action(widget, text, fn, *shortcuts):
action = QtGui.QAction(text, widget)
action.connect(action, SIGNAL('triggered()'), fn)
if shortcuts:
shortcuts = list(set(shortcuts))
action.setShortcuts(shortcuts)
action.setShortcutContext(Qt.WidgetWithChildrenShortcut)
widget.addAction(action)
return action
def set_selected_item(widget, idx):
"""Sets a the currently selected item to the item at index idx."""
if type(widget) is QtGui.QTreeWidget:
item = widget.topLevelItem(idx)
if item:
widget.setItemSelected(item, True)
widget.setCurrentItem(item)
def add_items(widget, items):
"""Adds items to a widget."""
for item in items:
widget.addItem(item)
def set_items(widget, items):
"""Clear the existing widget contents and set the new items."""
widget.clear()
add_items(widget, items)
def tr(txt):
"""Translate a string into a local language."""
if type(txt) is QtCore.QString:
# This has already been translated; leave as-is
return unicode(txt)
return unicode(QtGui.QApplication.instance().translate('', txt))
def icon_file(filename, staged=False, untracked=False):
"""Returns a file path representing a corresponding file path."""
if staged:
if os.path.exists(core.encode(filename)):
ifile = resources.icon('staged.png')
else:
ifile = resources.icon('removed.png')
elif untracked:
ifile = resources.icon('untracked.png')
else:
ifile = utils.file_icon(core.encode(filename))
return ifile
def icon_for_file(filename, staged=False, untracked=False):
"""Returns a QIcon for a particular file path."""
ifile = icon_file(filename, staged=staged, untracked=untracked)
return icon(ifile)
def create_treeitem(filename, staged=False, untracked=False, check=True):
"""Given a filename, return a QListWidgetItem suitable
for adding to a QListWidget. "staged" and "untracked"
controls whether to use the appropriate icons."""
if check:
ifile = icon_file(filename, staged=staged, untracked=untracked)
else:
ifile = resources.icon('staged.png')
return create_treewidget_item(filename, ifile)
def update_file_icons(widget, items, staged=True,
untracked=False, offset=0):
"""Populate a QListWidget with custom icon items."""
for idx, model_item in enumerate(items):
item = widget.item(idx+offset)
if item:
item.setIcon(icon_for_file(model_item, staged, untracked))
def set_listwidget_strings(widget, items):
"""Sets a list widget to the strings passed in items."""
widget.clear()
add_items(widget, [ QtGui.QListWidgetItem(i) for i in items ])
@memoize
def cached_icon(key):
"""Maintain a cache of standard icons and return cache entries."""
style = QtGui.QApplication.instance().style()
return style.standardIcon(key)
def dir_icon():
"""Return a standard icon for a directory."""
return cached_icon(QtGui.QStyle.SP_DirIcon)
def file_icon():
"""Return a standard icon for a file."""
return cached_icon(QtGui.QStyle.SP_FileIcon)
def apply_icon():
"""Return a standard Apply icon"""
return cached_icon(QtGui.QStyle.SP_DialogApplyButton)
def save_icon():
"""Return a standard Save icon"""
return cached_icon(QtGui.QStyle.SP_DialogSaveButton)
def ok_icon():
"""Return a standard Ok icon"""
return cached_icon(QtGui.QStyle.SP_DialogOkButton)
def open_icon():
"""Return a standard Save icon"""
return cached_icon(QtGui.QStyle.SP_DirOpenIcon)
def git_icon():
return icon('git.svg')
def reload_icon():
"""Returna standard Refresh icon"""
return cached_icon(QtGui.QStyle.SP_BrowserReload)
def discard_icon():
"""Return a standard Discard icon"""
return cached_icon(QtGui.QStyle.SP_DialogDiscardButton)
def close_icon():
"""Return a standard Close icon"""
return cached_icon(QtGui.QStyle.SP_DialogCloseButton)
def add_close_action(widget):
"""Adds close action and shortcuts to a widget."""
return add_action(widget, 'Close...',
widget.close, QtGui.QKeySequence.Close, 'Ctrl+Q')
def center_on_screen(widget):
"""Move widget to the center of the default screen"""
desktop = QtGui.QApplication.instance().desktop()
rect = desktop.screenGeometry(QtGui.QCursor().pos())
cy = rect.height()/2
cx = rect.width()/2
widget.move(cx - widget.width()/2, cy - widget.height()/2)
def save_state(widget):
if gitcfg.instance().get('cola.savewindowsettings', True):
settings.Settings().save_gui_state(widget)
def export_window_state(widget, state, version):
# Save the window state
windowstate = widget.saveState(version)
state['windowstate'] = unicode(windowstate.toBase64().data())
return state
def apply_window_state(widget, state, version):
# Restore the dockwidget, etc. window state
try:
windowstate = state['windowstate']
widget.restoreState(QtCore.QByteArray.fromBase64(str(windowstate)),
version)
except KeyError:
pass
def apply_state(widget):
state = settings.Settings().get_gui_state(widget)
widget.apply_state(state)
return bool(state)
@memoize
def theme_icon(name):
"""Grab an icon from the current theme with a fallback
Support older versions of Qt by catching AttributeError and
falling back to our default icons.
"""
try:
base, ext = os.path.splitext(name)
qicon = QtGui.QIcon.fromTheme(base)
if not qicon.isNull():
return qicon
except AttributeError:
pass
return icon(name)
+49
View File
@@ -0,0 +1,49 @@
"""Provides the prefix() function for finding cola resources"""
import os
import glob
_modpath = os.path.abspath(__file__)
if 'share' in __file__ and 'lib' in __file__:
# this is the release tree
# __file__ = '$prefix/share/git-cola/lib/cola/__file__.py'
_lib_dir = os.path.dirname(os.path.dirname(_modpath))
_prefix = os.path.dirname(os.path.dirname(os.path.dirname(_lib_dir)))
else:
# this is the source tree
# __file__ = '$prefix/cola/__file__.py'
_prefix = os.path.dirname(os.path.dirname(_modpath))
def prefix(*args):
"""Return a path relative to cola's installation prefix"""
return os.path.join(_prefix, *args)
def doc(*args):
"""Return a path relative to cola's /usr/share/doc/ directory"""
return os.path.join(_prefix, 'share', 'doc', 'git-cola', *args)
def html_docs():
"""Return the path to the cola html documentation."""
# index.html only exists after the install-docs target is run,
# so fallback to git-cola.txt.
htmldocs = doc('html', 'index.html')
if os.path.exists(htmldocs):
return htmldocs
return doc('git-cola.txt')
def share(*args):
"""Return a path relative to cola's /usr/share/ directory"""
return prefix('share', 'git-cola', *args)
def icon(basename):
"""Return the full path to an icon file given a basename."""
return 'icons:'+basename
def icon_dir():
"""Return the path to the style dir within the cola install tree."""
return share('icons')
+106
View File
@@ -0,0 +1,106 @@
# Copyright (c) 2008 David Aguilar
"""This handles saving complex settings such as bookmarks, etc.
"""
import os
import sys
try:
import simplejson
json = simplejson
except ImportError:
import json
class Settings(object):
_file = '~/.config/git-cola/settings'
def __init__(self):
"""Load existing settings if they exist"""
self.values = {}
self.load()
# properties
def _get_bookmarks(self):
try:
bookmarks = self.values['bookmarks']
except KeyError:
bookmarks = self.values['bookmarks'] = []
return bookmarks
def _get_gui_state(self):
try:
gui_state = self.values['gui_state']
except KeyError:
gui_state = self.values['gui_state'] = {}
return gui_state
bookmarks = property(_get_bookmarks)
gui_state = property(_get_gui_state)
def add_bookmark(self, bookmark):
"""Adds a bookmark to the saved settings"""
if bookmark not in self.bookmarks:
self.bookmarks.append(bookmark)
def remove_bookmark(self, bookmark):
"""Removes a bookmark from the saved settings"""
if bookmark in self.bookmarks:
self.bookmarks.remove(bookmark)
def path(self):
return os.path.expanduser(Settings._file)
def save(self):
path = self.path()
try:
parent = os.path.dirname(path)
if not os.path.isdir(parent):
os.makedirs(parent)
fp = open(path, 'wb')
json.dump(self.values, fp, indent=4)
fp.close()
except:
sys.stderr.write('git-cola: error writing "%s"\n' % path)
def load(self):
path = self.path()
if not os.path.exists(path):
self.load_dot_cola(path)
return
try:
fp = open(path, 'rb')
self.values = json.load(fp)
except: # bad json
pass
def load_dot_cola(self, path):
path = os.path.join(os.path.expanduser('~'), '.cola')
if not os.path.exists(path):
return
try:
fp = open(path, 'rb')
values = json.load(fp)
fp.close()
except: # bad json
return
for key in ('bookmarks', 'gui_state'):
try:
self.values[key] = values[key]
except KeyError:
pass
def save_gui_state(self, gui):
"""Saves settings for a cola view"""
name = gui.name()
state = gui.export_state()
self.gui_state[name] = state
self.save()
def get_gui_state(self, gui):
"""Returns the state for a gui"""
try:
state = self.gui_state[gui.name()]
except KeyError:
state = self.gui_state[gui.name()] = {}
return state
+64
View File
@@ -0,0 +1,64 @@
add_signoff = 'add_signoff'
amend = 'amend'
amend_mode = 'amend_mode'
apply_diff_selection = 'apply_diff_selection'
apply_patches = 'apply_patches'
branch_mode = 'branch_mode'
commit = 'commit'
commits_selected = 'commits_selected'
confirm = 'confirm'
critical = 'critical'
edit = 'edit'
checkout = 'checkout'
checkout_branch = 'checkout_branch'
cherry_pick = 'cherry_pick'
clone = 'clone'
delete = 'delete'
ignore = 'ignore'
delete_branch = 'delete_branch'
diff = 'diff'
diff_expr_mode = 'diff_expr_mode'
diff_mode = 'diff_mode'
diff_staged = 'diff_staged'
diffstat = 'diffstat'
difftool = 'difftool'
format_patch = 'format_patch'
grep = 'grep'
information = 'information'
log_cmd = 'log_cmd'
load_commit_message = 'load_commit_message'
load_commit_template = 'load_commit_template'
load_previous_message = 'load_previous_message'
mergetool = 'mergetool'
mode = 'mode'
modified_summary = 'modified_summary'
open_repo = 'open_repo'
question = 'question'
redo = 'redo'
rescan = 'rescan'
reset_mode = 'reset_mode'
review_branch_mode = 'review_branch_mode'
run_config_action = 'run_config_action'
run_command = 'run_command'
set_diff_text = 'set_diff_text'
show_untracked = 'show_untracked'
signoff = 'signoff'
stage = 'stage'
stage_diffs = 'stage_diffs'
stage_modified = 'stage_modified'
stage_untracked = 'stage_untracked'
staged_summary = 'staged_summary'
tag = 'tag'
unmerged_summary = 'unmerged_summary'
undo = 'undo'
undo_diffs = 'undo_diffs'
unstage = 'unstage'
unstage_diffs = 'unstage_diffs'
unstage_all = 'unstage_all'
unstage_selected = 'unstage_selected'
untracked_summary = 'untracked_summary'
update_status = 'update_status'
visualize_all = 'visualize_all'
visualize_current = 'visualize_current'
visualize_paths = 'visualize_paths'
visualize_revision = 'visualize_revision'
+24
View File
@@ -0,0 +1,24 @@
from cola import qtutils
from cola.stash.controller import StashController
from cola.stash.model import StashModel
from cola.stash.view import StashView
def stash():
"""Launches a stash dialog using the provided model + view
"""
model = StashModel()
view = StashView(model, qtutils.active_window())
ctl = StashController(model, view)
view.show()
return ctl
if __name__ == '__main__':
from cola.app import ColaApplication
import cola
cola.model().update_status()
app = ColaApplication([])
ctl = stash()
app.exec_()
+9
View File
@@ -0,0 +1,9 @@
"""This controller handles the stash dialog."""
from cola.ctrl import Controller
from cola.stash.model import command_directory
class StashController(Controller):
def __init__(self, model, view):
Controller.__init__(self, model, view)
self.add_commands(command_directory)
+83
View File
@@ -0,0 +1,83 @@
import cola
from cola import observable
from cola import signals
from cola.git import git
from cola.cmds import BaseCommand, Rescan, rescan
apply_stash = 'apply_stash'
drop_stash = 'drop_stash'
save_stash = 'save_stash'
class StashModel(observable.Observable):
def __init__(self):
observable.Observable.__init__(self)
def stash_list(self):
return git.stash('list').splitlines()
def has_stashable_changes(self):
model = cola.model()
return bool(model.modified + model.staged)
def stash_info(self, revids=False, names=False):
"""Parses "git stash list" and returns a list of stashes."""
stashes = self.stash_list()
revids = [s[:s.index(':')] for s in stashes]
names = [s.split(': ', 2)[-1] for s in stashes]
return stashes, revids, names
def stash_diff(self, rev):
diffstat = git.stash('show', rev)
diff = git.stash('show', '-p', rev)
return diffstat + '\n\n' + diff
class ApplyStash(BaseCommand):
def __init__(self, selection, index):
BaseCommand.__init__(self)
self.selection = selection
self.index = index
def do(self):
if self.index:
args = ['apply', '--index', self.selection]
else:
args = ['apply', self.selection]
status, output = git.stash(with_stderr=True, with_status=True, *args)
cola.notifier().broadcast(signals.log_cmd, status, output)
class DropStash(BaseCommand):
def __init__(self, stash_sha1):
BaseCommand.__init__(self)
self.stash_sha1 = stash_sha1
def do(self):
status, output = git.stash('drop', self.stash_sha1,
with_stderr=True, with_status=True)
cola.notifier().broadcast(signals.log_cmd, status, output)
class SaveStash(BaseCommand):
def __init__(self, stash_name, keep_index):
BaseCommand.__init__(self)
self.stash_name = stash_name
self.keep_index = keep_index
def do(self):
if self.keep_index:
args = ['save', '--keep-index', self.stash_name]
else:
args = ['save', self.stash_name]
status, output = git.stash(with_stderr=True, with_status=True, *args)
cola.notifier().broadcast(signals.log_cmd, status, output)
command_directory = {
apply_stash: ApplyStash,
drop_stash: DropStash,
rescan: Rescan,
save_stash: SaveStash,
}
+208
View File
@@ -0,0 +1,208 @@
"""Provides the StashView dialog."""
from PyQt4 import QtCore
from PyQt4 import QtGui
from PyQt4.QtCore import SIGNAL
from cola import qt
from cola import qtutils
from cola import utils
from cola.stash.model import save_stash, apply_stash, drop_stash, rescan
from cola.views import standard
from cola.widgets import defs
from cola.widgets.diff import DiffView
class StashView(standard.Dialog):
def __init__(self, model, parent=None):
standard.Dialog.__init__(self, parent=parent)
self.model = model
self.stashes = []
self.revids = []
self.names = []
self.setWindowModality(QtCore.Qt.WindowModal)
self.setWindowTitle(self.tr('Stash'))
if parent:
self.resize(parent.width(), 420)
else:
self.resize(700, 420)
self.stash_list = QtGui.QListWidget(self)
self.stash_view = DiffView(self)
self.button_apply =\
self.toolbutton(self.tr('Apply'),
self.tr('Apply the selected stash'),
qtutils.apply_icon())
self.button_save =\
self.toolbutton(self.tr('Save'),
self.tr('Save modified state to new stash'),
qtutils.save_icon())
self.button_drop = \
self.toolbutton(self.tr('Drop'),
self.tr('Drop the selected stash'),
qtutils.discard_icon())
self.button_close = \
self.pushbutton(self.tr('Close'),
self.tr('Close'), qtutils.close_icon())
self.keep_index = QtGui.QCheckBox(self)
self.keep_index.setText(self.tr('Keep Index'))
self.keep_index.setChecked(True)
self.setTabOrder(self.button_save, self.button_apply)
self.setTabOrder(self.button_apply, self.button_drop)
self.setTabOrder(self.button_drop, self.keep_index)
self.setTabOrder(self.keep_index, self.button_close)
# Arrange layouts
self.main_layt = QtGui.QVBoxLayout()
self.main_layt.setMargin(defs.margin)
self.main_layt.setSpacing(defs.spacing)
self.btn_layt = QtGui.QHBoxLayout()
self.btn_layt.setMargin(0)
self.btn_layt.setSpacing(defs.spacing)
self.splitter = QtGui.QSplitter()
self.splitter.setHandleWidth(defs.handle_width)
self.splitter.setOrientation(QtCore.Qt.Horizontal)
self.splitter.setChildrenCollapsible(True)
self.splitter.setStretchFactor(0, 1)
self.splitter.setStretchFactor(1, 1)
self.splitter.insertWidget(0, self.stash_list)
self.splitter.insertWidget(1, self.stash_view)
self.btn_layt.addWidget(self.button_save)
self.btn_layt.addWidget(self.button_apply)
self.btn_layt.addWidget(self.button_drop)
self.btn_layt.addWidget(self.keep_index)
self.btn_layt.addStretch()
self.btn_layt.addWidget(self.button_close)
self.main_layt.addWidget(self.splitter)
self.main_layt.addLayout(self.btn_layt)
self.setLayout(self.main_layt)
self.splitter.setSizes([self.width()/3, self.width()*2/3])
self.connect(self.button_apply, SIGNAL('clicked()'),
self.stash_apply)
self.connect(self.button_save, SIGNAL('clicked()'),
self.stash_save)
self.connect(self.button_drop, SIGNAL('clicked()'),
self.stash_drop)
self.connect(self.button_close, SIGNAL('clicked()'), self.close)
self.update_from_model()
self.update_actions()
self.connect(self.stash_list, SIGNAL('itemSelectionChanged()'),
self.item_selected)
def close(self):
self.accept()
self.emit(SIGNAL(rescan))
def toolbutton(self, text, tooltip, icon):
return qt.create_toolbutton(self,
text=text, tooltip=tooltip, icon=icon)
def pushbutton(self, text, tooltip, icon):
btn = QtGui.QPushButton(self)
btn.setText(self.tr(text))
btn.setToolTip(self.tr(tooltip))
btn.setIcon(icon)
return btn
def selected_stash(self):
"""Returns the stash name of the currently selected stash
"""
list_widget = self.stash_list
stash_list = self.revids
return qtutils.selected_item(list_widget, stash_list)
def selected_name(self):
list_widget = self.stash_list
stash_list = self.names
return qtutils.selected_item(list_widget, stash_list)
def item_selected(self):
"""Shows the current stash in the main view."""
self.update_actions()
selection = self.selected_stash()
if not selection:
return
diff_text = self.model.stash_diff(selection)
self.stash_view.setPlainText(diff_text)
def update_actions(self):
has_changes = self.model.has_stashable_changes()
has_stash = bool(self.selected_stash())
self.button_save.setEnabled(has_changes)
self.button_apply.setEnabled(has_stash)
self.button_drop.setEnabled(has_stash)
def update_from_model(self):
"""Initiates git queries on the model and updates the view
"""
stashes, revids, names = self.model.stash_info()
self.stashes = stashes
self.revids = revids
self.names = names
self.stash_list.clear()
self.stash_list.addItems(self.stashes)
def stash_apply(self):
"""Applies the currently selected stash
"""
selection = self.selected_stash()
if not selection:
return
index = self.keep_index.isChecked()
self.emit(SIGNAL(apply_stash), selection, index)
self.accept()
self.emit(SIGNAL(rescan))
def stash_save(self):
"""Saves the worktree in a stash
This prompts the user for a stash name and creates
a git stash named accordingly.
"""
stash_name, ok = qtutils.prompt('Save Stash',
'Enter a name for the stash')
if not ok or not stash_name:
return
# Sanitize the stash name
stash_name = utils.sanitize(stash_name)
if stash_name in self.names:
qtutils.critical('Oops!',
'A stash named "%s" already exists' % stash_name)
return
keep_index = self.keep_index.isChecked()
self.emit(SIGNAL(save_stash), stash_name, keep_index)
self.accept()
self.emit(SIGNAL(rescan))
def stash_drop(self):
"""Drops the currently selected stash
"""
selection = self.selected_stash()
name = self.selected_name()
if not selection:
return
if not qtutils.confirm('Drop Stash?',
'Recovering a dropped stash is not possible.',
'Drop the "%s" stash?' % name,
'Drop Stash',
default=False,
icon=qtutils.discard_icon()):
return
self.emit(SIGNAL(drop_stash), selection)
self.update_from_model()
self.stash_view.setPlainText('')
+382
View File
@@ -0,0 +1,382 @@
# Copyright (c) 2008 David Aguilar
"""This module provides miscellaneous utility functions."""
import os
import re
import sys
import errno
import platform
import shlex
import subprocess
import mimetypes
from cola import git
from cola import core
from cola import resources
from cola.compat import hashlib
from cola.decorators import memoize
KNOWN_FILE_MIME_TYPES = {
'text': 'script.png',
'image': 'image.png',
'python': 'script.png',
'ruby': 'script.png',
'shell': 'script.png',
'perl': 'script.png',
'octet': 'binary.png',
}
KNOWN_FILE_EXTENSION = {
'.java': 'script.png',
'.groovy': 'script.png',
'.cpp': 'script.png',
'.c': 'script.png',
'.h': 'script.png',
'.cxx': 'script.png',
}
def add_parents(path_entry_set):
"""Iterate over each item in the set and add its parent directories."""
for path in list(path_entry_set):
while '//' in path:
path = path.replace('//', '/')
if path not in path_entry_set:
path_entry_set.add(path)
if '/' in path:
parent_dir = dirname(path)
while parent_dir and parent_dir not in path_entry_set:
path_entry_set.add(parent_dir)
parent_dir = dirname(parent_dir)
return path_entry_set
def run_cmd(command):
"""
Run arguments as a command and return output.
>>> run_cmd(["echo", "hello", "world"])
'hello world'
"""
return git.Git.execute(command)
def ident_file_type(filename):
"""Returns an icon based on the contents of filename."""
if os.path.exists(filename):
filemimetype = mimetypes.guess_type(filename)
if filemimetype[0] != None:
for filetype, iconname in KNOWN_FILE_MIME_TYPES.iteritems():
if filetype in filemimetype[0].lower():
return iconname
filename = filename.lower()
for fileext, iconname in KNOWN_FILE_EXTENSION.iteritems():
if filename.endswith(fileext):
return iconname
return 'generic.png'
else:
return 'removed.png'
# Fallback for modified files of an unknown type
return 'generic.png'
def file_icon(filename):
"""
Returns the full path to an icon file corresponding to
filename"s contents.
"""
return resources.icon(ident_file_type(filename))
def fork(args):
"""Launch a command in the background."""
encoded_args = [core.encode(arg) for arg in args]
return subprocess.Popen(encoded_args).pid
def sublist(a,b):
"""Subtracts list b from list a and returns the resulting list."""
# conceptually, c = 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):
"""Greps a list for items that match a pattern and return a list of
matching items. If only one item matches, return just that item.
"""
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):
"""
An os.path.basename() implementation that always uses '/'
Avoid os.path.basename because git's output always
uses '/' regardless of platform.
"""
return path.rsplit('/', 1)[-1]
def dirname(path):
"""
An os.path.dirname() implementation that always uses '/'
Avoid os.path.dirname because git's output always
uses '/' regardless of platform.
"""
while '//' in path:
path = path.replace('//', '/')
path_dirname = path.rsplit('/', 1)[0]
if path_dirname == path:
return ''
return path.rsplit('/', 1)[0]
def slurp(path):
"""Slurps a filepath into a string."""
fh = open(core.encode(path))
slushy = core.read_nointr(fh)
fh.close()
return core.decode(slushy)
def write(path, contents):
"""Writes a raw string to a file."""
fh = open(core.encode(path), 'wb')
core.write_nointr(fh, core.encode(contents))
fh.close()
def strip_prefix(prefix, string):
"""Return string, without the prefix. Blow up if string doesn't
start with prefix."""
assert string.startswith(prefix)
return string[len(prefix):]
def sanitize(s):
"""Removes shell metacharacters from a string."""
for c in """ \t!@#$%^&*()\\;,<>"'[]{}~|""":
s = s.replace(c, '_')
return s
def shell_split(s):
"""Split string apart into utf-8 encoded words using shell syntax"""
try:
return shlex.split(core.encode(s))
except ValueError:
return [s]
def shell_usplit(s):
"""Returns a unicode list instead of encoded strings"""
return [core.decode(arg) for arg in shell_split(s)]
def is_linux():
"""Is this a linux machine?"""
while True:
try:
return platform.system() == 'Linux'
except IOError, e:
if e.errno == errno.EINTR:
continue
raise e
def is_debian():
"""Is it debian?"""
return os.path.exists('/usr/bin/apt-get')
def is_darwin():
"""Return True on OSX."""
while True:
try:
p = platform.platform()
break
except IOError, e:
if e.errno == errno.EINTR:
continue
raise e
p = p.lower()
return 'macintosh' in p or 'darwin' in p
@memoize
def is_win32():
"""Return True on win32"""
return os.name in ('nt', 'dos')
def win32_set_binary(fd):
try:
import msvcrt
except ImportError:
return
# When run without console, pipes may expose invalid
# fileno(), usually set to -1.
if hasattr(fd, 'fileno') and fd.fileno() >= 0:
msvcrt.setmode(fd.fileno(), os.O_BINARY)
def posix_set_binary(fd):
"""POSIX file descriptors are always binary"""
pass
if is_win32():
set_binary = win32_set_binary
else:
set_binary = posix_set_binary
def checksum(path):
"""Return a cheap md5 hexdigest for a path."""
md5 = hashlib.new('md5')
md5.update(slurp(path))
return md5.hexdigest()
def error(msg, *args):
"""Print an error message to stderr."""
print >> sys.stderr, "ERROR:", msg % args
def warn(msg, *args):
"""Print a warning message to stderr."""
print >> sys.stderr, "warning:", msg % args
def die(msg, *args):
"""Print as error message to stderr and exit the program."""
error(msg, *args)
sys.exit(1)
class ProgressIndicator(object):
"""Simple progress indicator.
Displayed as a spinning character by default, but can be customized
by passing custom messages that overrides the spinning character.
"""
States = ("|", "/", "-", "\\")
def __init__(self, prefix="", f=sys.stdout):
"""Create a new ProgressIndicator, bound to the given file object."""
self.n = 0 # Simple progress counter
self.f = f # Progress is written to this file object
self.prev_len = 0 # Length of previous msg (to be overwritten)
self.prefix = prefix # Prefix prepended to each progress message
self.prefix_lens = [] # Stack of prefix string lengths
def pushprefix(self, prefix):
"""Append the given prefix onto the prefix stack."""
self.prefix_lens.append(len(self.prefix))
self.prefix += prefix
def popprefix(self):
"""Remove the last prefix from the prefix stack."""
prev_len = self.prefix_lens.pop()
self.prefix = self.prefix[:prev_len]
def __call__(self, msg = None, lf=False):
"""Indicate progress, possibly with a custom message."""
if msg is None:
msg = self.States[self.n % len(self.States)]
msg = self.prefix + msg
print >> self.f, "\r%-*s" % (self.prev_len, msg),
self.prev_len = len(msg.expandtabs())
if lf:
print >> self.f
self.prev_len = 0
self.n += 1
def finish (self, msg="done", noprefix=False):
"""Finalize progress indication with the given message."""
if noprefix:
self.prefix = ""
self(msg, True)
def start_command(args, cwd=None, shell=False, add_env=None,
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE):
"""Start the given command, and return a subprocess object.
This provides a simpler interface to the subprocess module.
"""
env = None
if add_env is not None:
env = os.environ.copy()
env.update(add_env)
return subprocess.Popen(args, bufsize=1, stdin=stdin, stdout=stdout,
stderr=stderr, cwd=cwd, shell=shell,
env=env, universal_newlines=True)
def run_command(args, cwd=None, shell=False, add_env=None,
flag_error=True):
"""Run the given command to completion, and return its results.
This provides a simpler interface to the subprocess module.
The results are formatted as a 3-tuple: (exit_code, output, errors)
If flag_error is enabled, Error messages will be produced if the
subprocess terminated with a non-zero exit code and/or stderr
output.
The other arguments are passed on to start_command().
"""
process = start_command(args, cwd, shell, add_env)
(output, errors) = process.communicate()
exit_code = process.returncode
if flag_error and errors:
error("'%s' returned errors:\n---\n%s---", " ".join(args), errors)
if flag_error and exit_code:
error("'%s' returned exit code %i", " ".join(args), exit_code)
return (exit_code, output, errors)
+76
View File
@@ -0,0 +1,76 @@
# Copyright (c) 2012 David Aguilar
"""Provide cola's version number"""
# The current git-cola version
_default_version = '1.7.1'
import os
import sys
if __name__ == '__main__':
srcdir = os.path.dirname(os.path.dirname(__file__))
sys.path.insert(1, srcdir)
from cola.git import git
from cola.decorators import memoize
# minimum version requirements
_versions = {
# git-diff learned --patience in 1.6.2
# git-mergetool learned --no-prompt in 1.6.2
# git-difftool moved out of contrib in git 1.6.3
'git': '1.6.3',
'python': '2.4',
'pyqt': '4.4',
'pyqt_qrunnable': '4.4',
'diff-submodule': '1.6.6',
}
def get(key):
"""Returns an entry from the known versions table"""
return _versions.get(key)
def version():
"""Returns the current version"""
return _default_version
@memoize
def check_version(min_ver, ver):
"""Check whether ver is greater or equal to min_ver
"""
min_ver_list = version_to_list(min_ver)
ver_list = version_to_list(ver)
return min_ver_list <= ver_list
@memoize
def check(key, ver):
"""Checks if a version is greater than the known version for <what>"""
return check_version(get(key), ver)
def version_to_list(version):
"""Convert a version string to a list of numbers or strings
"""
ver_list = []
for p in version.split('.'):
try:
n = int(p)
except ValueError:
n = p
ver_list.append(n)
return ver_list
@memoize
def git_version():
"""Returns the current GIT version"""
return git.version().split()[-1]
if __name__ == '__main__':
print(version())
View File
+171
View File
@@ -0,0 +1,171 @@
"""Provides dialogs for comparing branches and commits."""
from PyQt4 import QtCore
from PyQt4 import QtGui
from PyQt4.QtCore import SIGNAL
from cola.views import standard
from cola.widgets import defs
class CompareView(standard.Dialog):
def __init__(self, parent=None):
standard.Dialog.__init__(self, parent=parent)
self.setWindowTitle(self.tr('Compare Commits'))
self.resize(649, 372)
self._main_layt = QtGui.QVBoxLayout(self)
self._top_layt = QtGui.QHBoxLayout()
self._top_left_layt = QtGui.QVBoxLayout()
self._top_left_layt.setMargin(0)
self._top_left_layt.setSpacing(defs.spacing)
# Exposed
self.descriptions_start = QtGui.QTreeWidget(self)
self.descriptions_start.setRootIsDecorated(False)
self.descriptions_start.setAllColumnsShowFocus(True)
self.descriptions_start.headerItem().setText(0,
self.tr('Start Commit'))
self._top_left_layt.addWidget(self.descriptions_start)
# Exposed
self.revision_start = QtGui.QLineEdit(self)
self.revision_start.setReadOnly(True)
self._top_left_layt.addWidget(self.revision_start)
self._top_layt.addLayout(self._top_left_layt)
self._top_right_layt = QtGui.QVBoxLayout()
self._top_right_layt.setMargin(0)
self._top_right_layt.setSpacing(defs.spacing)
# Exposed
self.descriptions_end = QtGui.QTreeWidget(self)
self.descriptions_end.setRootIsDecorated(False)
self.descriptions_end.setAllColumnsShowFocus(True)
self.descriptions_end.headerItem().setText(0, self.tr('End Commit'))
self._top_right_layt.addWidget(self.descriptions_end)
# Exposed
self.revision_end = QtGui.QLineEdit(self)
self.revision_end.setReadOnly(True)
self._top_right_layt.addWidget(self.revision_end)
self._top_layt.addLayout(self._top_right_layt)
self._main_layt.addLayout(self._top_layt)
# Exposed
self.compare_files = QtGui.QTreeWidget(self)
self.compare_files.setAlternatingRowColors(True)
self.compare_files.setRootIsDecorated(False)
self.compare_files.setAllColumnsShowFocus(True)
self.compare_files.headerItem().setText(0, self.tr('File Differences'))
self._main_layt.addWidget(self.compare_files)
self._bottom_layt = QtGui.QHBoxLayout()
self._bottom_layt.setMargin(0)
self._bottom_layt.setSpacing(defs.spacing)
self.show_versions = QtGui.QCheckBox(self)
self.show_versions.setText(self.tr('Show Versions'))
self._bottom_layt.addWidget(self.show_versions)
self._num_results_label = QtGui.QLabel(self)
self._num_results_label.setText(self.tr('Num Results'))
self._bottom_layt.addWidget(self._num_results_label)
# Exposed
self.num_results = QtGui.QSpinBox(self)
self.num_results.setMinimum(1)
self.num_results.setMaximum(9999)
self.num_results.setProperty('value', QtCore.QVariant(100))
self._bottom_layt.addWidget(self.num_results)
self._bottom_spacer = QtGui.QSpacerItem(1, 1,
QtGui.QSizePolicy.Expanding,
QtGui.QSizePolicy.Minimum)
self._bottom_layt.addItem(self._bottom_spacer)
self.button_compare = QtGui.QPushButton(self)
self.button_compare.setText(self.tr('Compare'))
self._bottom_layt.addWidget(self.button_compare)
self.button_close = QtGui.QPushButton(self)
self.button_close.setText(self.tr('Close'))
self._bottom_layt.addWidget(self.button_close)
self._main_layt.addLayout(self._bottom_layt)
self.connect(self.button_close, SIGNAL('clicked()'), self.accept)
class BranchCompareView(standard.Dialog):
def __init__(self, parent=None):
standard.Dialog.__init__(self, parent=parent)
self.setWindowTitle(self.tr('Branch Diff Viewer'))
self.resize(658, 350)
self._main_layt = QtGui.QVBoxLayout(self)
self._main_layt.setMargin(defs.margin)
self._main_layt.setSpacing(defs.spacing)
self._splitter = QtGui.QSplitter(self)
self._splitter.setOrientation(QtCore.Qt.Vertical)
self._splitter.setHandleWidth(defs.handle_width)
self._top_widget = QtGui.QWidget(self._splitter)
self._top_grid_layt = QtGui.QGridLayout(self._top_widget)
self._top_grid_layt.setMargin(0)
self._top_grid_layt.setSpacing(defs.spacing)
# Exposed
self.left_combo = QtGui.QComboBox(self._top_widget)
self.left_combo.addItem(self.tr('Local'))
self.left_combo.addItem(self.tr('Remote'))
self._top_grid_layt.addWidget(self.left_combo, 0, 0, 1, 1)
# Exposed
self.right_combo = QtGui.QComboBox(self._top_widget)
self.right_combo.addItem(self.tr('Local'))
self.right_combo.addItem(self.tr('Remote'))
self.right_combo.setCurrentIndex(1)
self._top_grid_layt.addWidget(self.right_combo, 0, 1, 1, 1)
# Exposed
self.left_list = QtGui.QListWidget(self._top_widget)
self._top_grid_layt.addWidget(self.left_list, 1, 0, 1, 1)
# Exposed
self.right_list = QtGui.QListWidget(self._top_widget)
self._top_grid_layt.addWidget(self.right_list, 1, 1, 1, 1)
self._bottom_widget = QtGui.QWidget(self._splitter)
self._bottom_grid_layt = QtGui.QGridLayout(self._bottom_widget)
self._bottom_grid_layt.setMargin(0)
self._bottom_grid_layt.setSpacing(defs.spacing)
self._button_spacer = QtGui.QSpacerItem(1, 1,
QtGui.QSizePolicy.Expanding,
QtGui.QSizePolicy.Minimum)
self._bottom_grid_layt.addItem(self._button_spacer, 1, 1, 1, 1)
# Exposed
self.button_compare = QtGui.QPushButton(self._bottom_widget)
self.button_compare.setText(self.tr('Compare'))
self._bottom_grid_layt.addWidget(self.button_compare, 1, 2, 1, 1)
# Exposed
self.button_close = QtGui.QPushButton(self._bottom_widget)
self.button_close.setText(self.tr('Close'))
self._bottom_grid_layt.addWidget(self.button_close, 1, 3, 1, 1)
# Exposed
self.diff_files = QtGui.QTreeWidget(self._bottom_widget)
self.diff_files.headerItem().setText(0, self.tr('File Differences'))
self._bottom_grid_layt.addWidget(self.diff_files, 0, 0, 1, 4)
self._main_layt.addWidget(self._splitter)
self.connect(self.button_close, SIGNAL('clicked()'), self.accept)
+165
View File
@@ -0,0 +1,165 @@
from PyQt4 import QtGui
from PyQt4.QtCore import SIGNAL
from cola import qtutils
from cola.views import standard
from cola.widgets import defs
class CreateBranchView(standard.Dialog):
"""A dialog for creating branches."""
def __init__(self, parent=None):
standard.Dialog.__init__(self, parent=parent)
self.setWindowTitle(self.tr('Create Branch'))
self.resize(555, 333)
self._main_layt = QtGui.QVBoxLayout(self)
self._main_layt.setMargin(defs.margin)
self._main_layt.setSpacing(defs.spacing)
self._branch_name_layt = QtGui.QHBoxLayout()
self._branch_name_label = QtGui.QLabel(self)
self._branch_name_label.setText(self.tr('Branch Name'))
self._branch_name_layt.addWidget(self._branch_name_label)
# Exposed
self.local_branch = QtGui.QLineEdit(self)
self._branch_name_layt.addWidget(self.local_branch)
self._main_layt.addLayout(self._branch_name_layt)
self._rev_start_grp = QtGui.QGroupBox(self)
self._rev_start_grp.setTitle(self.tr('Starting Revision'))
self._rev_start_vbox_layt = QtGui.QVBoxLayout(self._rev_start_grp)
self._rev_start_vbox_layt.setMargin(defs.margin)
self._rev_start_vbox_layt.setSpacing(defs.spacing)
self._rev_start_textinput_layt = QtGui.QHBoxLayout()
self._rev_start_textinput_layt.setSpacing(defs.spacing)
self._rev_label = QtGui.QLabel(self._rev_start_grp)
self._rev_label.setText(self.tr('Revision Expression:'))
self._rev_start_textinput_layt.addWidget(self._rev_label)
# Exposed
self.revision = QtGui.QLineEdit(self._rev_start_grp)
self._rev_start_textinput_layt.addWidget(self.revision)
self._rev_start_vbox_layt.addLayout(self._rev_start_textinput_layt)
self._rev_start_radiobtn_layt = QtGui.QHBoxLayout()
# Exposed
self.local_radio = QtGui.QRadioButton(self._rev_start_grp)
self.local_radio.setText(self.tr('Local Branch'))
self.local_radio.setChecked(True)
self._rev_start_radiobtn_layt.addWidget(self.local_radio)
# Exposed
self.remote_radio = QtGui.QRadioButton(self._rev_start_grp)
self.remote_radio.setText(self.tr('Tracking Branch'))
self._rev_start_radiobtn_layt.addWidget(self.remote_radio)
# Exposed
self.tag_radio = QtGui.QRadioButton(self._rev_start_grp)
self.tag_radio.setText(self.tr('Tag'))
self._rev_start_radiobtn_layt.addWidget(self.tag_radio)
self._radio_spacer = QtGui.QSpacerItem(1, 1,
QtGui.QSizePolicy.Expanding,
QtGui.QSizePolicy.Minimum)
self._rev_start_radiobtn_layt.addItem(self._radio_spacer)
self._rev_start_vbox_layt.addLayout(self._rev_start_radiobtn_layt)
# Exposed
self.branch_list = QtGui.QListWidget(self._rev_start_grp)
self._rev_start_vbox_layt.addWidget(self.branch_list)
self._main_layt.addWidget(self._rev_start_grp)
self._options_section_layt = QtGui.QHBoxLayout()
self._options_section_layt.setMargin(defs.margin)
self._options_section_layt.setSpacing(defs.spacing)
self._option_grpbox = QtGui.QGroupBox(self)
self._option_grpbox.setTitle(self.tr('Options'))
self._options_grp_layt = QtGui.QVBoxLayout(self._option_grpbox)
self._options_grp_layt.setMargin(defs.margin)
self._options_grp_layt.setSpacing(defs.spacing)
self._options_radio_layt = QtGui.QHBoxLayout()
self._update_existing_label = QtGui.QLabel(self._option_grpbox)
self._update_existing_label.setText(self.tr('Update Existing Branch:'))
self._options_radio_layt.addWidget(self._update_existing_label)
# Exposed
self.no_update_radio = QtGui.QRadioButton(self._option_grpbox)
self.no_update_radio.setText(self.tr('No'))
self._options_radio_layt.addWidget(self.no_update_radio)
# Exposed
self.ffwd_only_radio = QtGui.QRadioButton(self._option_grpbox)
self.ffwd_only_radio.setText(self.tr('Fast Forward Only'))
self.ffwd_only_radio.setChecked(True)
self._options_radio_layt.addWidget(self.ffwd_only_radio)
# Exposed
self.reset_radio = QtGui.QRadioButton(self._option_grpbox)
self.reset_radio.setText(self.tr('Reset'))
self._options_radio_layt.addWidget(self.reset_radio)
self._options_grp_layt.addLayout(self._options_radio_layt)
self._options_bottom_layt = QtGui.QHBoxLayout()
self._options_checkbox_layt = QtGui.QVBoxLayout()
self.fetch_checkbox = QtGui.QCheckBox(self._option_grpbox)
self.fetch_checkbox.setText(self.tr('Fetch Tracking Branch'))
self.fetch_checkbox.setChecked(True)
self._options_checkbox_layt.addWidget(self.fetch_checkbox)
self.checkout_checkbox = QtGui.QCheckBox(self._option_grpbox)
self.checkout_checkbox.setText(self.tr('Checkout After Creation'))
self.checkout_checkbox.setChecked(True)
self._options_checkbox_layt.addWidget(self.checkout_checkbox)
self._options_bottom_layt.addLayout(self._options_checkbox_layt)
self._options_spacer = QtGui.QSpacerItem(1, 1,
QtGui.QSizePolicy.Expanding,
QtGui.QSizePolicy.Minimum)
self._options_bottom_layt.addItem(self._options_spacer)
self._options_grp_layt.addLayout(self._options_bottom_layt)
self._options_section_layt.addWidget(self._option_grpbox)
self._buttons_layt = QtGui.QHBoxLayout()
self._buttons_layt.setMargin(defs.margin)
self._buttons_layt.setSpacing(defs.spacing)
# Exposed
self.create_button = QtGui.QPushButton(self)
self.create_button.setText(self.tr('Create Branch'))
self.create_button.setIcon(qtutils.git_icon())
self.create_button.setDefault(True)
self._buttons_layt.addWidget(self.create_button)
# Exposed
self.cancel_button = QtGui.QPushButton(self)
self.cancel_button.setText(self.tr('Cancel'))
self._buttons_layt.addWidget(self.cancel_button)
self._options_section_layt.addLayout(self._buttons_layt)
self._main_layt.addLayout(self._options_section_layt)
self.connect(self.cancel_button, SIGNAL('pressed()'), self.reject)
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
createbranch = CreateBranchView()
createbranch.show()
sys.exit(app.exec_())
+88
View File
@@ -0,0 +1,88 @@
from PyQt4 import QtGui
from PyQt4 import QtCore
from PyQt4.QtCore import SIGNAL
from cola import qtutils
from cola.prefs import diff_font
from cola.views import standard
class CreateTag(standard.Dialog):
def __init__(self, parent):
standard.Dialog.__init__(self, parent=parent)
self.setWindowModality(QtCore.Qt.WindowModal)
self.setWindowTitle(self.tr('Create Tag'))
self.resize(506, 295)
self._main_layt = QtGui.QVBoxLayout(self)
self._main_layt.setContentsMargins(6, 12, 6, 6)
# Form layout for inputs
self._input_form_layt = QtGui.QFormLayout()
self._input_form_layt.setFieldGrowthPolicy(QtGui.QFormLayout.ExpandingFieldsGrow)
# Tag label
self._tag_name_label = QtGui.QLabel(self)
self._tag_name_label.setText(self.tr('Name'))
self._input_form_layt.setWidget(0, QtGui.QFormLayout.LabelRole,
self._tag_name_label)
self.tag_name = QtGui.QLineEdit(self)
self.tag_name.setToolTip(self.tr('Specifies the tag name'))
self._input_form_layt.setWidget(0, QtGui.QFormLayout.FieldRole,
self.tag_name)
# Sign Tag
self._sign_label = QtGui.QLabel(self)
self._sign_label.setText(self.tr('Sign Tag'))
self._input_form_layt.setWidget(1, QtGui.QFormLayout.LabelRole,
self._sign_label)
self.sign_tag = QtGui.QCheckBox(self)
self.sign_tag.setToolTip(
self.tr('Whether to sign the tag (git tag -s)'))
self._input_form_layt.setWidget(1, QtGui.QFormLayout.FieldRole,
self.sign_tag)
self._main_layt.addLayout(self._input_form_layt)
# Tag message
self._tag_msg_label = QtGui.QLabel(self)
self._tag_msg_label.setText(self.tr('Message'))
self._input_form_layt.setWidget(2, QtGui.QFormLayout.LabelRole,
self._tag_msg_label)
# Exposed
self.tag_msg = QtGui.QTextEdit(self)
self.tag_msg.setAcceptRichText(False)
self.tag_msg.setToolTip(self.tr('Specifies the tag message'))
self.tag_msg.setFont(diff_font())
self._input_form_layt.setWidget(2, QtGui.QFormLayout.FieldRole,
self.tag_msg)
# Revision
self._rev_label = QtGui.QLabel(self)
self._rev_label.setText(self.tr('Revision'))
self._input_form_layt.setWidget(3, QtGui.QFormLayout.LabelRole,
self._rev_label)
# Exposed
self.revision = QtGui.QComboBox(self)
self.revision.setEditable(True)
self.revision.setToolTip(self.tr('Specifies the SHA-1 to tag'))
self._input_form_layt.setWidget(3, QtGui.QFormLayout.FieldRole,
self.revision)
# Buttons
self._button_hbox_layt = QtGui.QHBoxLayout()
self._button_spacer = QtGui.QSpacerItem(1, 1,
QtGui.QSizePolicy.Expanding,
QtGui.QSizePolicy.Minimum)
self._button_hbox_layt.addItem(self._button_spacer)
self.create_button = QtGui.QPushButton(self)
self.create_button.setText(self.tr('Create Tag'))
self.create_button.setIcon(qtutils.git_icon())
self._button_hbox_layt.addWidget(self.create_button)
self._main_layt.addLayout(self._button_hbox_layt)
self.close_button = QtGui.QPushButton(self)
self.close_button.setText(self.tr('Close'))
self._button_hbox_layt.addWidget(self.close_button)
self.connect(self.close_button, SIGNAL('clicked()'), self.accept)
+161
View File
@@ -0,0 +1,161 @@
from PyQt4 import QtCore
from PyQt4 import QtGui
from PyQt4.QtCore import SIGNAL
from cola import qtutils
from cola.views import standard
from cola.widgets import defs
class RemoteView(standard.Dialog):
"""A dialog for choosing branches."""
def __init__(self, parent, action):
"""Customizes the dialog based on the remote action
"""
standard.Dialog.__init__(self, parent=parent)
self.setWindowModality(QtCore.Qt.WindowModal)
self.resize(666, 420)
self._main_vbox_layt = QtGui.QVBoxLayout(self)
self._main_vbox_layt.setMargin(defs.margin)
self._main_vbox_layt.setSpacing(defs.spacing)
# Local branch section
self._local_branch_hbox_layt = QtGui.QHBoxLayout()
# Exposed
self.local_label = QtGui.QLabel(self)
self.local_label.setText(self.tr('Local Branches'))
# Exposed
self.local_branch = QtGui.QLineEdit(self)
# Exposed
self.local_branches = QtGui.QListWidget(self)
# Remote branch section
self._remote_branch_hbox_layt = QtGui.QHBoxLayout()
self._remote_label = QtGui.QLabel(self)
self._remote_label.setText(self.tr('Remote'))
# Exposed
self.remotename = QtGui.QLineEdit(self)
# Exposed
self.remotes = QtGui.QListWidget(self)
self._remote_branches_hbox_layt = QtGui.QHBoxLayout()
# Exposed
self.remote_label = QtGui.QLabel(self)
self.remote_label.setText(self.tr('Remote Branch'))
# Exposed
self.remote_branch = QtGui.QLineEdit(self)
self.remote_branches = QtGui.QListWidget(self)
self._options_hbox_layt = QtGui.QHBoxLayout()
self._options_hbox_layt.setSpacing(defs.spacing)
self._options_hbox_layt.addStretch()
# Exposed
self.ffwd_only_checkbox = QtGui.QCheckBox(self)
self.ffwd_only_checkbox.setText(self.tr('Fast Forward Only '))
self.ffwd_only_checkbox.setChecked(True)
self.ffwd_only_checkbox.setObjectName("ffwd_only_checkbox")
# Exposed
self.tags_checkbox = QtGui.QCheckBox(self)
self.tags_checkbox.setText(self.tr('Include tags '))
self.rebase_checkbox = QtGui.QCheckBox(self)
self.rebase_checkbox.setText(self.tr('Rebase '))
# Exposed
self.action_button = QtGui.QPushButton(self)
self.action_button.setText(self.tr('Push'))
self.action_button.setIcon(qtutils.ok_icon())
# Exposed
self.close_button = QtGui.QPushButton(self)
self.close_button.setText(self.tr('Close'))
self.close_button.setIcon(qtutils.close_icon())
# connections
self.connect(self.close_button, SIGNAL('released()'), self.reject)
if action:
self.action_button.setText(action.title())
self.setWindowTitle(action.title())
if action == 'pull':
self.tags_checkbox.hide()
self.ffwd_only_checkbox.hide()
self.local_label.hide()
self.local_branch.hide()
self.local_branches.hide()
self.remote_branch.setFocus()
else:
self.rebase_checkbox.hide()
self.remotename.setFocus()
self.layout_remotes()
if action == 'push':
self.layout_local_branches()
self.layout_remote_branches()
else: # fetch and pull
self.layout_remote_branches()
self.layout_local_branches()
self.layout_options()
def layout_options(self):
self._options_hbox_layt.addWidget(self.ffwd_only_checkbox)
self._options_hbox_layt.addWidget(self.tags_checkbox)
self._options_hbox_layt.addWidget(self.rebase_checkbox)
self._options_hbox_layt.addWidget(self.action_button)
self._options_hbox_layt.addWidget(self.close_button)
self._main_vbox_layt.addLayout(self._options_hbox_layt)
def layout_local_branches(self):
self._local_branch_hbox_layt.addWidget(self.local_label)
self._local_branch_hbox_layt.addWidget(self.local_branch)
self._main_vbox_layt.addLayout(self._local_branch_hbox_layt)
self._main_vbox_layt.addWidget(self.local_branches)
def layout_remotes(self):
self._remote_branch_hbox_layt.addWidget(self._remote_label)
self._remote_branch_hbox_layt.addWidget(self.remotename)
self._main_vbox_layt.addLayout(self._remote_branch_hbox_layt)
self._main_vbox_layt.addWidget(self.remotes)
def layout_remote_branches(self):
self._remote_branches_hbox_layt.addWidget(self.remote_label)
self._remote_branches_hbox_layt.addWidget(self.remote_branch)
self._main_vbox_layt.addLayout(self._remote_branches_hbox_layt)
self._main_vbox_layt.addWidget(self.remote_branches)
def select_first_remote(self):
"""Selects the first remote in the list view"""
return self.select_remote(0)
def select_remote(self, idx):
"""Selects a remote by index"""
item = self.remotes.item(idx)
if item:
self.remotes.setItemSelected(item, True)
self.remotes.setCurrentItem(item)
self.remotename.setText(item.text())
return True
else:
return False
def select_local_branch(self, idx):
"""Selects a local branch by index in the list view"""
item = self.local_branches.item(idx)
if not item:
return False
self.local_branches.setItemSelected(item, True)
self.local_branches.setCurrentItem(item)
self.local_branch.setText(item.text())
return True
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
remote = RemoteView()
remote.show()
sys.exit(app.exec_())
+140
View File
@@ -0,0 +1,140 @@
"""Provides the SearchView class."""
from PyQt4 import QtGui
from PyQt4 import QtCore
from PyQt4.QtCore import SIGNAL
from cola.prefs import diff_font
from cola.qt import DiffSyntaxHighlighter
from cola.views import standard
from cola.widgets import defs
class SearchView(standard.Dialog):
def __init__(self, parent=None):
standard.Dialog.__init__(self, parent=parent)
self.setWindowTitle(self.tr('Search'))
if self.parent():
self.resize(600, self.parent().height())
else:
self.resize(600, 500)
self._main_vbox_layt = QtGui.QVBoxLayout(self)
self._top_grid_layt = QtGui.QGridLayout()
# Exposed
self.radio_range = QtGui.QRadioButton(self)
self.radio_range.setText(self.tr('Revision Range Expression'))
self._top_grid_layt.addWidget(self.radio_range, 0, 0, 1, 2)
# Exposed
self.radio_path = QtGui.QRadioButton(self)
self.radio_path.setText(self.tr('Commits Touching Paths'))
self._top_grid_layt.addWidget(self.radio_path, 0, 2, 1, 1)
# Exposed
self.radio_author = QtGui.QRadioButton(self)
self.radio_author.setText(self.tr('Author'))
self._top_grid_layt.addWidget(self.radio_author, 0, 3, 1, 1)
# Exposed
self.radio_committer = QtGui.QRadioButton(self)
self.radio_committer.setText(self.tr('Committer'))
self._top_grid_layt.addWidget(self.radio_committer, 0, 4, 1, 1)
# Exposed
self.radio_revision = QtGui.QRadioButton(self)
self.radio_revision.setText(self.tr('Revision ID'))
self._top_grid_layt.addWidget(self.radio_revision, 1, 0, 1, 2)
# Exposed
self.radio_message = QtGui.QRadioButton(self)
self.radio_message.setText(self.tr('Commit Messages'))
self._top_grid_layt.addWidget(self.radio_message, 1, 2, 1, 1)
# Exposed
self.radio_daterange = QtGui.QRadioButton(self)
self.radio_daterange.setText(self.tr('Date Range (Start / End)'))
self._top_grid_layt.addWidget(self.radio_daterange, 1, 3, 1, 2)
# Exposed
self.maxresults_label = QtGui.QLabel(self)
self.maxresults_label.setText(self.tr('Max Results'))
self._top_grid_layt.addWidget(self.maxresults_label, 2, 0, 1, 1)
# Exposed
self.max_results = QtGui.QSpinBox(self)
self.max_results.setMinimum(5)
self.max_results.setMaximum(9995)
self.max_results.setSingleStep(5)
self.max_results.setProperty("value", QtCore.QVariant(500))
self._top_grid_layt.addWidget(self.max_results, 2, 1, 1, 1)
# Exposed
self.radio_diff = QtGui.QRadioButton(self)
self.radio_diff.setText(self.tr('Diff Content'))
self._top_grid_layt.addWidget(self.radio_diff, 2, 2, 1, 1)
# Exposed
self.start_date = QtGui.QDateEdit(self)
self.start_date.setCurrentSection(QtGui.QDateTimeEdit.YearSection)
self.start_date.setCalendarPopup(True)
self.start_date.setDisplayFormat(self.tr('yyyy-MM-dd'))
self._top_grid_layt.addWidget(self.start_date, 2, 3, 1, 1)
# Exposed
self.end_date = QtGui.QDateEdit(self)
self.end_date.setCurrentSection(QtGui.QDateTimeEdit.YearSection)
self.end_date.setCalendarPopup(True)
self.end_date.setDisplayFormat(self.tr('yyyy-MM-dd'))
self._top_grid_layt.addWidget(self.end_date, 2, 4, 1, 1)
# Exposed
self.button_search = QtGui.QPushButton(self)
self.button_search.setText(self.tr('Search'))
self.button_search.setShortcut(self.tr('Return'))
self._top_grid_layt.addWidget(self.button_search, 3, 0, 1, 1)
# Exposed
self.query = QtGui.QLineEdit(self)
self.query.setFocus()
self._top_grid_layt.addWidget(self.query, 3, 1, 1, 3)
# Exposed
self.button_browse = QtGui.QPushButton(self)
self.button_browse.setText(self.tr('Browse'))
self._top_grid_layt.addWidget(self.button_browse, 3, 4, 1, 1)
self._main_vbox_layt.addLayout(self._top_grid_layt)
# Exposed
self.splitter = QtGui.QSplitter(self)
self.splitter.setHandleWidth(defs.handle_width)
self.splitter.setOrientation(QtCore.Qt.Vertical)
# Exposed
self.commit_list = QtGui.QListWidget(self.splitter)
self.commit_list.setMinimumSize(QtCore.QSize(1, 1))
self.commit_list.setAlternatingRowColors(True)
self.commit_list.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
# Exposed
self.commit_text = QtGui.QTextEdit(self.splitter)
self.commit_text.setMinimumSize(QtCore.QSize(1, 1))
self.commit_text.setTabChangesFocus(True)
self.commit_text.setReadOnly(True)
self.commit_text.setAcceptRichText(False)
self.commit_text.setFont(diff_font())
self._syntax = DiffSyntaxHighlighter(self.commit_text.document(),
whitespace=False)
self._main_vbox_layt.addWidget(self.splitter)
self._button_grid_layt = QtGui.QGridLayout()
# Exposed
self.button_export = QtGui.QPushButton(self)
self.button_export.setText(self.tr('Export Patches'))
self._button_grid_layt.addWidget(self.button_export, 0, 0, 1, 1)
# Exposed
self.button_cherrypick = QtGui.QPushButton(self)
self.button_cherrypick.setText(self.tr('Cherry Pick'))
self._button_grid_layt.addWidget(self.button_cherrypick, 0, 1, 1, 1)
# Exposed
self._button_spacer = QtGui.QSpacerItem(111, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
self._button_grid_layt.addItem(self._button_spacer, 0, 2, 1, 1)
# Exposed
self.button_close = QtGui.QPushButton(self)
self.button_close.setText(self.tr('Close'))
self._button_grid_layt.addWidget(self.button_close, 0, 3, 1, 1)
self._main_vbox_layt.addLayout(self._button_grid_layt)
self.connect(self.button_close, SIGNAL('clicked()'), self.accept)
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
search = SearchView()
search.show()
sys.exit(app.exec_())
+84
View File
@@ -0,0 +1,84 @@
from PyQt4 import QtGui
from PyQt4 import QtCore
from PyQt4.QtCore import SIGNAL
from cola.prefs import diff_font
from cola.qt import DiffSyntaxHighlighter
from cola.widgets import defs
class SelectCommitsView(QtGui.QDialog):
def __init__(self,
parent=None,
title=None,
multiselect=True,
syntax=True):
QtGui.QDialog.__init__(self, parent)
if title:
self.setWindowTitle(title)
# Allow disabling multi-select
self.resize(700, 420)
self.setObjectName('commit')
self.vboxlayout = QtGui.QVBoxLayout(self)
self.vboxlayout.setObjectName('vboxlayout')
self.splitter = QtGui.QSplitter(self)
self.splitter.setOrientation(QtCore.Qt.Vertical)
self.splitter.setHandleWidth(defs.handle_width)
self.splitter.setObjectName('splitter')
self.commit_list = QtGui.QListWidget(self.splitter)
self.commit_list.setObjectName('commit_list')
self.commit_list.setAlternatingRowColors(True)
if multiselect:
mode = QtGui.QAbstractItemView.ExtendedSelection
else:
mode = QtGui.QAbstractItemView.SingleSelection
self.commit_list.setSelectionMode(mode)
self.commit_text = QtGui.QTextEdit(self.splitter)
self.commit_text.setMinimumSize(QtCore.QSize(0, 40))
self.commit_text.setTabChangesFocus(True)
self.commit_text.setUndoRedoEnabled(False)
self.commit_text.setLineWrapMode(QtGui.QTextEdit.NoWrap)
self.commit_text.setReadOnly(True)
self.commit_text.setObjectName('commit_text')
self.vboxlayout.addWidget(self.splitter)
self.hboxlayout = QtGui.QHBoxLayout()
self.hboxlayout.setObjectName("hboxlayout")
self.label = QtGui.QLabel(self)
self.label.setObjectName('label')
self.label.setText(self.tr('Revision Expression:'))
self.hboxlayout.addWidget(self.label)
self.revision = QtGui.QLineEdit(self)
self.revision.setObjectName('revision')
self.hboxlayout.addWidget(self.revision)
self.vboxlayout.addLayout(self.hboxlayout)
self.button_box = QtGui.QDialogButtonBox(self)
self.button_box.setStandardButtons(QtGui.QDialogButtonBox.Cancel |
QtGui.QDialogButtonBox.Ok)
self.button_box.setObjectName('button_box')
self.vboxlayout.addWidget(self.button_box)
self.connect(self.button_box, SIGNAL('accepted()'), self.accept)
self.connect(self.button_box, SIGNAL('rejected()'), self.reject)
self.setTabOrder(self.button_box, self.commit_list)
self.setTabOrder(self.commit_list, self.revision)
self.setTabOrder(self.revision, self.commit_text)
# Make the list widget slighty larger
self.splitter.setSizes([100, 150])
self.syntax = DiffSyntaxHighlighter(self.commit_text.document(),
whitespace=False)
# Set the console font
if syntax:
self.commit_text.setFont(diff_font())
+56
View File
@@ -0,0 +1,56 @@
from PyQt4 import QtGui
def create_standard_widget(qtclass):
"""Create a standard widget derived from a qt class.
"""
class StandardWidget(qtclass):
# Mix-in for standard view operations
def __init__(self, parent=None):
self._qtclass = qtclass
self._qtclass.__init__(self, parent)
def show(self):
"""Automatically centers dialogs"""
if self.parent():
left = self.parent().x()
width = self.parent().width()
center_x = left + width/2
x = center_x - self.width()/2
y = self.parent().y()
self.move(x, y)
# Call the base Qt show()
self._qtclass.show(self)
def name(self):
"""Returns the name of the view class"""
return self.__class__.__name__.lower()
def apply_state(self, state):
"""Imports data for view save/restore"""
try:
self.resize(state['width'], state['height'])
except:
pass
try:
self.move(state['x'], state['y'])
except:
pass
def export_state(self):
"""Exports data for view save/restore"""
return {
'x': self.x(),
'y': self.y(),
'width': self.width(),
'height': self.height(),
}
return StandardWidget
# The base class for all cola QDialogs.
Dialog = create_standard_widget(QtGui.QDialog)
MainWindow = create_standard_widget(QtGui.QMainWindow)
View File
+79
View File
@@ -0,0 +1,79 @@
from PyQt4 import QtGui
from PyQt4 import QtCore
from PyQt4.QtCore import SIGNAL
from cola import qtutils
from cola import version
def launch_about_dialog():
"""Launches the Help -> About dialog"""
view = AboutView(qtutils.active_window())
view.show()
view.set_version(version.version())
COPYRIGHT = """git-cola: The highly caffeinated git GUI v$VERSION
git-cola is a sweet, carbonated git GUI known for its
sugary flavor and caffeine-inspired features.
Copyright (C) 2007-2012, David Aguilar and contributors
This program is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License
version 2 as published by the Free Software Foundation.
This program is distributed in the hope that it will
be useful, but WITHOUT ANY WARRANTY; without even the
implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the
GNU General Public License along with this program.
If not, see http://www.gnu.org/licenses/.
"""
class AboutView(QtGui.QDialog):
"""Provides the git-cola 'About' dialog.
"""
def __init__(self, parent=None):
QtGui.QDialog.__init__(self, parent)
self.setWindowTitle('About git-cola')
self.setStyleSheet('QWidget { background-color: white; color: #666; }')
self.setMinimumSize(QtCore.QSize(280, 420))
self.setMaximumSize(QtCore.QSize(280, 420))
self.ham = QtGui.QLabel(self)
self.ham.setGeometry(QtCore.QRect(0, -10, 291, 121))
self.ham.setPixmap(QtGui.QPixmap('images:logo-cola.png'))
self.spam = QtGui.QLabel(self)
self.spam.setGeometry(QtCore.QRect(10, 110, 261, 261))
font = QtGui.QFont()
font.setFamily('Sans Serif')
font.setPointSize(5)
self.spam.setFont(font)
self.spam.setTextFormat(QtCore.Qt.LogText)
self.spam.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop)
self.spam.setWordWrap(True)
self.spam.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse)
self.spam.setText(COPYRIGHT)
self.kthx = QtGui.QPushButton(self)
self.kthx.setGeometry(QtCore.QRect(60, 380, 160, 24))
self.kthx.setText('kthx bye')
self.kthx.setDefault(True)
self.connect(self.kthx, SIGNAL('clicked()'), self.accept)
def set_version(self, version):
"""Sets the version field in the 'about' dialog"""
self.spam.setText(self.spam.text().replace('$VERSION', version))
+191
View File
@@ -0,0 +1,191 @@
from PyQt4 import QtGui
from PyQt4 import QtCore
from PyQt4.QtCore import SIGNAL
import os
import sys
if __name__ == '__main__':
sys.path.insert(1,
os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
from cola import qtutils
from cola.git import git
from cola.qt import QCollapsibleGroupBox
from cola.widgets import defs
class GitArchiveDialog(QtGui.QDialog):
@staticmethod
def create(ref, parent=None):
dlg = GitArchiveDialog(ref, parent=parent)
if dlg.exec_() != dlg.Accepted:
return None
return dlg
def __init__(self, ref, parent=None):
QtGui.QDialog.__init__(self, parent)
self.setWindowModality(QtCore.Qt.WindowModal)
# input
self.ref = ref
# outputs
self.fmt = None
self.prefix = ''
self.filename = ref
# widgets
self.setWindowTitle('Save Archive')
self.filetext = QtGui.QLineEdit()
self.filetext.setText(ref)
self.browse = QtGui.QToolButton()
self.browse.setAutoRaise(True)
style = self.style()
self.browse.setIcon(style.standardIcon(QtGui.QStyle.SP_DirIcon))
self.format_strings = git.archive('--list').rstrip().splitlines()
self.format_combo = QtGui.QComboBox()
self.format_combo.setEditable(False)
self.format_combo.addItems(self.format_strings)
self.cancel = QtGui.QPushButton()
self.cancel.setText('Cancel')
self.save = QtGui.QPushButton()
self.save.setText('Save')
self.save.setDefault(True)
self.prefix_label = QtGui.QLabel()
self.prefix_label.setText('Prefix')
self.prefix_text = QtGui.QLineEdit()
self.prefix_group = QCollapsibleGroupBox(parent=self)
self.prefix_group.setTitle('Advanced')
# layouts
self.filelayt = QtGui.QHBoxLayout()
self.filelayt.setMargin(0)
self.filelayt.setSpacing(defs.spacing)
self.filelayt.addWidget(self.browse)
self.filelayt.addWidget(self.filetext)
self.filelayt.addWidget(self.format_combo)
self.prefixlayt = QtGui.QHBoxLayout()
self.prefixlayt.setMargin(defs.margin)
self.prefixlayt.setSpacing(defs.spacing)
self.prefixlayt.addWidget(self.prefix_label)
self.prefixlayt.addWidget(self.prefix_text)
self.prefix_group.setLayout(self.prefixlayt)
self.prefix_group.set_collapsed(True)
self.btnlayt = QtGui.QHBoxLayout()
self.btnlayt.setMargin(0)
self.btnlayt.setSpacing(defs.spacing)
self.btnlayt.addStretch()
self.btnlayt.addWidget(self.cancel)
self.btnlayt.addWidget(self.save)
self.mainlayt = QtGui.QVBoxLayout()
self.mainlayt.setMargin(defs.margin)
self.mainlayt.setSpacing(0)
self.mainlayt.addLayout(self.filelayt)
self.mainlayt.addWidget(self.prefix_group)
self.mainlayt.addStretch()
self.mainlayt.addLayout(self.btnlayt)
self.setLayout(self.mainlayt)
self.resize(555, 0)
# initial setup; done before connecting to avoid
# signal/slot side-effects
if 'tar.gz' in self.format_strings:
idx = self.format_strings.index('tar.gz')
elif 'zip' in self.format_strings:
idx = self.format_strings.index('zip')
else:
idx = 0
self.format_combo.setCurrentIndex(idx)
self.update_filetext_for_format(idx)
# connections
self.connect(self.filetext, SIGNAL('textChanged(QString)'),
self.filetext_changed)
self.connect(self.prefix_text, SIGNAL('textChanged(QString)'),
self.prefix_text_changed)
self.connect(self.format_combo, SIGNAL('currentIndexChanged(int)'),
self.update_filetext_for_format)
self.connect(self.prefix_group, SIGNAL('toggled(bool)'),
self.prefix_group_toggled)
self.connect(self.browse, SIGNAL('clicked()'), self.choose_filename)
self.connect(self.cancel, SIGNAL('clicked()'), self.reject)
self.connect(self.save, SIGNAL('clicked()'), self.save_archive)
def save_archive(self):
filename = self.filename
if not filename:
return
if os.path.exists(filename):
title = 'Overwrite File?'
msg = 'The file "%s" exists and will be overwritten.' % filename
info_txt = 'Overwrite "%s"?' % filename
ok_txt = 'Overwrite'
icon = qtutils.save_icon()
if not qtutils.confirm(title, msg, info_txt, ok_txt,
default=False, icon=icon):
return
self.accept()
def choose_filename(self):
filename = qtutils.save_as(self.filename)
if not filename:
return
self.filetext.setText(filename)
self.update_filetext_for_format(self.format_combo.currentIndex())
def filetext_changed(self, qstr):
self.filename = unicode(qstr)
self.save.setEnabled(bool(self.filename))
def prefix_text_changed(self, qstr):
self.prefix = unicode(qstr)
def update_filetext_for_format(self, idx):
self.fmt = self.format_strings[idx]
text = unicode(self.filetext.text())
for format_string in self.format_strings:
ext = '.'+format_string
if text.endswith(ext):
text = text[:-len(ext)]
break
self.filename = '%s.%s' % (text, self.fmt)
self.filetext.setText(self.filename)
self.filetext.setFocus(True)
if '/' in text:
start = text.rindex('/') + 1
else:
start = 0
self.filetext.setSelection(start, len(text) - start)
def prefix_group_toggled(self, toggled):
if toggled:
self.filetext.setFocus(True)
else:
self.prefix_text.setFocus(True)
if __name__ == '__main__':
from cola.app import ColaApplication
app = ColaApplication([])
dlg = GitArchiveDialog('master')
dlg.show()
dlg.raise_()
app.exec_()
+412
View File
@@ -0,0 +1,412 @@
from PyQt4 import QtGui
from PyQt4 import QtCore
from PyQt4.QtCore import SIGNAL
import cola
from cola import cmdfactory
from cola import core
from cola import utils
from cola import qtutils
from cola import signals
from cola.cmds import BaseCommand
from cola.ctrl import Controller
from cola.git import git
from cola.widgets import defs
save_blob = 'save_blob'
class BrowseModel(object):
def __init__(self, ref):
self.ref = ref
self.relpath = None
self.filename = None
class SaveBlob(BaseCommand):
def __init__(self):
BaseCommand.__init__(self)
self.factory = cmdfactory.factory()
def do(self):
context = self.context
ref = core.encode(context.ref)
relpath = core.encode(context.relpath)
cmd = ['git', 'show', '%s:%s' % (ref, relpath)]
fp = open(core.encode(context.filename), 'wb')
proc = utils.start_command(cmd, stdout=fp)
out, err = proc.communicate()
fp.close()
status = proc.returncode
msg = ('Saved "%s" from %s to "%s"' %
(context.relpath, context.ref, context.filename))
cola.notifier().broadcast(signals.log_cmd, status, msg)
self.factory.prompt_user(signals.information,
'File Saved',
'File saved to "%s"' % context.filename)
class BrowseDialogController(Controller):
def __init__(self, model, view):
Controller.__init__(self, model, view)
command_directory = {
save_blob: SaveBlob,
}
self.add_commands(command_directory)
class BrowseDialog(QtGui.QDialog):
@staticmethod
def browse(ref):
parent = qtutils.active_window()
model = BrowseModel(ref)
dlg = BrowseDialog(model, parent=parent)
dlg_model = GitTreeModel(ref, dlg)
dlg.setModel(dlg_model)
dlg.setWindowTitle('Browsing %s' % model.ref)
ctrl = BrowseDialogController(model, dlg)
dlg.resize(parent.width()*3/4, 333)
dlg.show()
dlg.raise_()
if dlg.exec_() != dlg.Accepted:
return None
return ctrl
@staticmethod
def select_file(ref):
parent = qtutils.active_window()
model = BrowseModel(ref)
dlg = BrowseDialog(model, select_file=True, parent=parent)
dlg_model = GitTreeModel(ref, dlg)
dlg.setModel(dlg_model)
dlg.setWindowTitle('Select File from %s' % model.ref)
dlg.resize(parent.width()*3/4, 333)
dlg.show()
dlg.raise_()
if dlg.exec_() != dlg.Accepted:
return None
return model.filename
@staticmethod
def select_file_from_list(file_list):
parent = qtutils.active_window()
model = BrowseModel(None)
dlg = BrowseDialog(model, select_file=True, parent=parent)
dlg_model = GitFileTreeModel(dlg)
dlg_model.add_files(file_list)
dlg.setModel(dlg_model)
dlg.expandAll()
dlg.setWindowTitle('Select File from %s' % model.ref)
dlg.resize(parent.width()*3/4, 333)
dlg.show()
dlg.raise_()
if dlg.exec_() != dlg.Accepted:
return None
return model.filename
def __init__(self, model, select_file=False, parent=None):
QtGui.QDialog.__init__(self, parent)
self.setWindowModality(QtCore.Qt.WindowModal)
# updated for use by commands
self.model = model
# widgets
self.tree = GitTreeWidget(model.ref, parent=self)
self.close = QtGui.QPushButton('Close')
self.save = QtGui.QPushButton(select_file and 'Select' or 'Save')
self.save.setDefault(True)
self.save.setEnabled(False)
# layouts
self.btnlayt = QtGui.QHBoxLayout()
self.btnlayt.addStretch()
self.btnlayt.addWidget(self.close)
self.btnlayt.addWidget(self.save)
self.layt = QtGui.QVBoxLayout()
self.layt.setMargin(defs.margin)
self.layt.setSpacing(defs.spacing)
self.layt.addWidget(self.tree)
self.layt.addLayout(self.btnlayt)
self.setLayout(self.layt)
# connections
self.connect(self.close, SIGNAL('clicked()'), self.reject)
self.connect(self.save, SIGNAL('clicked()'), self.save_blob)
if select_file:
self.connect(self.tree, SIGNAL('path_chosen'), self.path_chosen)
else:
self.connect(self.tree, SIGNAL('path_chosen'), self.save_path)
self.connect(self.tree, SIGNAL('selectionChanged()'),
self.selection_changed)
def expandAll(self):
self.tree.expandAll()
def setModel(self, model):
self.tree.setModel(model)
def path_chosen(self, path, close=True):
"""Update the model from the view"""
model = self.model
model.relpath = path
model.filename = path
if close:
self.accept()
def save_path(self, path):
"""Choose an output filename based on the selected path"""
self.path_chosen(path, close=False)
model = self.model
filename = qtutils.save_as(model.filename)
if not filename:
return
model.filename = filename
self.emit(SIGNAL(save_blob))
self.accept()
def save_blob(self):
"""Save the currently selected file"""
filenames = self.tree.selected_files()
if not filenames:
return
self.path_chosen(filenames[0], close=True)
def selection_changed(self):
"""Update actions based on the current selection"""
filenames = self.tree.selected_files()
self.save.setEnabled(bool(filenames))
class GitTreeWidget(QtGui.QTreeView):
def __init__(self, ref, parent=None):
QtGui.QTreeView.__init__(self, parent)
self.setHeaderHidden(True)
self.setAlternatingRowColors(True)
self.setAllColumnsShowFocus(True)
self.setUniformRowHeights(True)
self.connect(self, SIGNAL('doubleClicked(const QModelIndex &)'),
self.double_clicked)
def double_clicked(self, index):
item = self.model().itemFromIndex(index)
if item is None:
return
if item.is_dir:
return
self.emit(SIGNAL('path_chosen'), item.path)
def selected_files(self):
items = map(self.model().itemFromIndex, self.selectedIndexes())
return [i.path for i in items if not i.is_dir]
def selectionChanged(self, old_selection, new_selection):
QtGui.QTreeView.selectionChanged(self, old_selection, new_selection)
self.emit(SIGNAL('selectionChanged()'))
def keyPressEvent(self, event):
"""
Override keyPressEvent to allow LeftArrow to work on non-directories.
When LeftArrow is pressed on a file entry or an unexpanded directory,
then move the current index to the parent directory.
This simplifies navigation using the keyboard.
For power-users, we support Vim keybindings ;-P
"""
# Check whether the item is expanded before calling the base class
# keyPressEvent otherwise we end up collapsing and changing the
# current index in one shot, which we don't want to do.
index = self.currentIndex()
was_expanded = self.isExpanded(index)
was_collapsed = not was_expanded
# Vim keybindings...
# Rewrite the event before marshalling to QTreeView.event()
key = event.key()
# Remap 'H' to 'Left'
if key == QtCore.Qt.Key_H:
event = QtGui.QKeyEvent(event.type(),
QtCore.Qt.Key_Left,
event.modifiers())
# Remap 'J' to 'Down'
elif key == QtCore.Qt.Key_J:
event = QtGui.QKeyEvent(event.type(),
QtCore.Qt.Key_Down,
event.modifiers())
# Remap 'K' to 'Up'
elif key == QtCore.Qt.Key_K:
event = QtGui.QKeyEvent(event.type(),
QtCore.Qt.Key_Up,
event.modifiers())
# Remap 'L' to 'Right'
elif key == QtCore.Qt.Key_L:
event = QtGui.QKeyEvent(event.type(),
QtCore.Qt.Key_Right,
event.modifiers())
# Re-read the event key to take the remappings into account
key = event.key()
# Process the keyPressEvent before changing the current index
# otherwise the event will affect the new index set here
# instead of the original index.
result = QtGui.QTreeView.keyPressEvent(self, event)
# Try to select the first item if the model index is invalid
if not index.isValid():
index = self.model().index(0, 0, QtCore.QModelIndex())
if index.isValid():
self.setCurrentIndex(index)
# Automatically select the first entry when expanding a directory
elif (key == QtCore.Qt.Key_Right and was_collapsed and
self.isExpanded(index)):
index = self.moveCursor(self.MoveDown, event.modifiers())
self.setCurrentIndex(index)
# Process non-root entries with valid parents only.
elif key == QtCore.Qt.Key_Left and index.parent().isValid():
# File entries have rowCount() == 0
if self.model().itemFromIndex(index).rowCount() == 0:
self.setCurrentIndex(index.parent())
# Otherwise, do this for collapsed directories only
elif was_collapsed:
self.setCurrentIndex(index.parent())
return result
class GitFileTreeModel(QtGui.QStandardItemModel):
"""Presents a list of file paths as a hierarchical tree."""
def __init__(self, parent):
QtGui.QStandardItemModel.__init__(self, parent)
self.dir_entries = {'': self.invisibleRootItem()}
self.dir_rows = {}
def add_files(self, files):
"""Add a list of files"""
add_file = self.add_file
for f in files:
add_file(f)
def add_file(self, path):
"""Add a file to the model."""
dirname = utils.dirname(path)
dir_entries = self.dir_entries
try:
parent = dir_entries[dirname]
except KeyError:
parent = dir_entries[dirname] = self.create_dir_entry(dirname)
row_items = self.create_row(path, False)
parent.appendRow(row_items)
def add_directory(self, parent, path):
"""Add a directory entry to the model."""
# Create model items
row_items = self.create_row(path, True)
# Insert directories before file paths
try:
row = self.dir_rows[parent]
except KeyError:
row = self.dir_rows[parent] = 0
parent.insertRow(row, row_items)
self.dir_rows[parent] += 1
self.dir_entries[path] = row_items[0]
return row_items[0]
def create_row(self, path, is_dir):
"""Return a list of items representing a row."""
return [GitTreeItem(path, is_dir)]
def create_dir_entry(self, dirname):
"""
Create a directory entry for the model.
This ensures that directories are always listed before files.
"""
entries = dirname.split('/')
curdir = []
parent = self.invisibleRootItem()
curdir_append = curdir.append
self_add_directory = self.add_directory
dir_entries = self.dir_entries
for entry in entries:
curdir_append(entry)
path = '/'.join(curdir)
try:
parent = dir_entries[path]
except KeyError:
grandparent = parent
parent = self_add_directory(grandparent, path)
dir_entries[path] = parent
return parent
class GitTreeModel(GitFileTreeModel):
def __init__(self, ref, parent):
GitFileTreeModel.__init__(self, parent)
self.ref = ref
self._initialize()
def _initialize(self):
"""Iterate over git-ls-tree and create GitTreeItems."""
status, output = git.ls_tree('--full-tree', '-r', '-t', '-z', self.ref,
with_status=True, with_stderr=True)
if status != 0:
cola.notifier().broadcast(signals.log_cmd, status, output)
return
for line in output.rstrip('\0').split('\0'):
# .....6 ...4 ......................................40
# 040000 tree c127cde9a0c644a3a8fef449a244f47d5272dfa6 relative
# 100644 blob 139e42bf4acaa4927ec9be1ec55a252b97d3f1e2 relative/path
objtype = line[7]
relpath = line[6 + 1 + 4 + 1 + 40 + 1:]
if objtype == 't':
parent = self.dir_entries[utils.dirname(relpath)]
self.add_directory(parent, relpath)
elif objtype == 'b':
self.add_file(relpath)
class GitTreeItem(QtGui.QStandardItem):
"""
Represents a cell in a treeview.
Many GitRepoItems could map to a single repository path,
but this tree only has a single column.
Each GitRepoItem manages a different cell in the tree view.
"""
def __init__(self, path, is_dir):
QtGui.QStandardItem.__init__(self)
self.is_dir = is_dir
self.path = path
self.setEditable(False)
self.setDragEnabled(False)
self.setText(utils.basename(path))
if is_dir:
self.setIcon(qtutils.dir_icon())
else:
self.setIcon(qtutils.file_icon())
+325
View File
@@ -0,0 +1,325 @@
import os
from PyQt4 import QtCore
from PyQt4 import QtGui
from PyQt4.QtCore import SIGNAL
import cola
from cola import core
from cola import gitcfg
from cola import gitcmds
from cola import qt
from cola import qtutils
from cola import signals
from cola.qt import GitRefLineEdit
from cola.views import standard
from cola.widgets import defs
def install_command_wrapper():
cmd_wrapper = ActionCommandWrapper()
cola.factory().add_command_wrapper(cmd_wrapper)
def get_config_actions():
cfg = gitcfg.instance()
names = cfg.get_guitool_names()
return names or []
def run_command(title, command):
"""Show a command widget"""
view = GitCommandWidget(qtutils.active_window())
view.setWindowModality(QtCore.Qt.ApplicationModal)
view.set_command(command)
view.setWindowTitle(title)
view.show()
view.raise_()
view.run()
view.exec_()
return (view.exitstatus, view.out, view.err)
class GitCommandWidget(standard.Dialog):
"""Nice TextView that reads the output of a command syncronously"""
# Keep us in scope otherwise PyQt kills the widget
def __init__(self, parent=None):
standard.Dialog.__init__(self, parent)
self.resize(720, 420)
# Construct the process
self.proc = QtCore.QProcess(self)
self.exitstatus = 0
self.out = ''
self.err = ''
self._layout = QtGui.QVBoxLayout(self)
self._layout.setContentsMargins(3, 3, 3, 3)
# Create the text browser
self.output_text = QtGui.QTextBrowser(self)
self.output_text.setAcceptDrops(False)
self.output_text.setTabChangesFocus(True)
self.output_text.setUndoRedoEnabled(False)
self.output_text.setReadOnly(True)
self.output_text.setAcceptRichText(False)
self._layout.addWidget(self.output_text)
# Create abort / close buttons
self.button_abort = QtGui.QPushButton(self)
self.button_abort.setText(self.tr('Abort'))
self.button_close = QtGui.QPushButton(self)
self.button_close.setText(self.tr('Close'))
# Put them in a horizontal layout at the bottom.
self.button_box = QtGui.QDialogButtonBox(self)
self.button_box.addButton(self.button_abort, QtGui.QDialogButtonBox.RejectRole)
self.button_box.addButton(self.button_close, QtGui.QDialogButtonBox.AcceptRole)
self._layout.addWidget(self.button_box)
# Connect the signals to the process
self.connect(self.proc, SIGNAL('readyReadStandardOutput()'), self.readOutput)
self.connect(self.proc, SIGNAL('readyReadStandardError()'), self.readErrors)
self.connect(self.proc, SIGNAL('finished(int)'), self.finishProc)
self.connect(self.proc, SIGNAL('stateChanged(QProcess::ProcessState)'), self.stateChanged)
# Connect the signlas to the buttons
self.connect(self.button_abort, SIGNAL('clicked()'), self.abortProc)
self.connect(self.button_close, SIGNAL('clicked()'), self.close)
# Start with abort disabled - will be enabled when the process is run.
self.button_abort.setEnabled(False)
def set_command(self, command):
self.command = command
def run(self):
"""Runs the process"""
self.proc.start(self.command[0], QtCore.QStringList(self.command[1:]))
def readOutput(self):
rawbytes = self.proc.readAllStandardOutput()
data = ''
for b in rawbytes:
data += b
self.out += data
self.append_text(data)
def readErrors(self):
rawbytes = self.proc.readAllStandardError()
data = ''
for b in rawbytes:
data += b
self.err += data
self.append_text(data)
def append_text(self, txt):
cursor = self.output_text.textCursor()
cursor.movePosition(cursor.End)
cursor.insertText(core.decode(txt))
cursor.movePosition(cursor.End)
self.output_text.setTextCursor(cursor)
def abortProc(self):
if self.proc.state() != QtCore.QProcess.NotRunning:
# Terminate seems to do nothing in windows
self.proc.terminate()
# Kill the process.
QtCore.QTimer.singleShot(1000, self.proc, QtCore.SLOT('kill()'))
def closeEvent(self, event):
if self.proc.state() != QtCore.QProcess.NotRunning:
# The process is still running, make sure we really want to abort.
title = 'Abort Action'
msg = ('An action is still running.\n'
'Terminating it could result in data loss.')
info_text = 'Abort the action?'
ok_text = 'Abort Action'
if qtutils.confirm(title, msg, info_text, ok_text,
default=False, icon=qtutils.discard_icon()):
self.abortProc()
event.accept()
else:
event.ignore()
else:
event.accept()
return standard.Dialog.closeEvent(self, event)
def stateChanged(self, newstate):
# State of process has changed - change the abort button state.
if newstate == QtCore.QProcess.NotRunning:
self.button_abort.setEnabled(False)
else:
self.button_abort.setEnabled(True)
def finishProc(self, status ):
self.exitstatus = status
class ActionCommandWrapper(object):
def __init__(self):
self.callbacks = {
signals.run_config_action: self.run_config_action,
signals.run_command: run_command,
}
def run_config_action(self, name, opts):
dlg = ActionDialog(qtutils.active_window(), name, opts)
dlg.show()
if dlg.exec_() != QtGui.QDialog.Accepted:
return False
rev = unicode(dlg.revision())
if rev:
opts['revision'] = rev
args = unicode(dlg.args())
if args:
opts['args'] = args
return True
class ActionDialog(standard.Dialog):
def __init__(self, parent, name, opts):
standard.Dialog.__init__(self, parent)
self.setWindowModality(QtCore.Qt.ApplicationModal)
self.name = name
self.opts = opts
self.layt = QtGui.QVBoxLayout()
self.layt.setMargin(defs.margin)
self.layt.setSpacing(defs.spacing)
self.setLayout(self.layt)
title = opts.get('title')
if title:
self.setWindowTitle(os.path.expandvars(title))
self.prompt = QtGui.QLabel()
prompt = opts.get('prompt')
if prompt:
self.prompt.setText(os.path.expandvars(prompt))
self.layt.addWidget(self.prompt)
self.argslabel = QtGui.QLabel()
if 'argprompt' not in opts or opts.get('argprompt') is True:
argprompt = qtutils.tr('Arguments')
else:
argprompt = opts.get('argprompt')
self.argslabel.setText(argprompt)
self.argstxt = QtGui.QLineEdit()
self.argslayt = QtGui.QHBoxLayout()
self.argslayt.addWidget(self.argslabel)
self.argslayt.addWidget(self.argstxt)
self.layt.addLayout(self.argslayt)
if not self.opts.get('argprompt'):
self.argslabel.setMinimumSize(1, 1)
self.argstxt.setMinimumSize(1, 1)
self.argstxt.hide()
self.argslabel.hide()
revs = (
('Local Branch', gitcmds.branch_list(remote=False)),
('Tracking Branch', gitcmds.branch_list(remote=True)),
('Tag', gitcmds.tag_list()),
)
if 'revprompt' not in opts or opts.get('revprompt') is True:
revprompt = qtutils.tr('Revision')
else:
revprompt = opts.get('revprompt')
self.revselect = RevisionSelector(self, revs)
self.revselect.set_revision_label(revprompt)
self.layt.addWidget(self.revselect)
if not opts.get('revprompt'):
self.revselect.hide()
# Close/Run buttons
self.btnlayt = QtGui.QHBoxLayout()
self.btnlayt.addStretch()
self.closebtn = qt.create_button(self.tr('Close'), self.btnlayt)
self.runbtn = qt.create_button(self.tr('Run'), self.btnlayt)
self.runbtn.setDefault(True)
self.layt.addLayout(self.btnlayt)
self.connect(self.closebtn, SIGNAL('clicked()'), self.reject)
self.connect(self.runbtn, SIGNAL('clicked()'), self.accept)
# Widen the dialog by default
self.resize(666, self.height())
def revision(self):
return self.revselect.revision()
def args(self):
return self.argstxt.text()
class RevisionSelector(QtGui.QWidget):
def __init__(self, parent, revs):
QtGui.QWidget.__init__(self, parent)
self._revs = revs
self._revdict = dict(revs)
self._layt = QtGui.QVBoxLayout()
self._layt.setMargin(0)
self.setLayout(self._layt)
self._rev_layt = QtGui.QHBoxLayout()
self._rev_layt.setMargin(0)
self._rev_label = QtGui.QLabel()
self._rev_layt.addWidget(self._rev_label)
self._revision = GitRefLineEdit()
self._rev_layt.addWidget(self._revision)
self._layt.addLayout(self._rev_layt)
self._radio_layt = QtGui.QHBoxLayout()
self._radio_btns = {}
# Create the radio buttons
for label, rev_list in self._revs:
radio = QtGui.QRadioButton()
radio.setText(self.tr(label))
radio.setObjectName(label)
self.connect(radio, SIGNAL('clicked()'), self._set_revision_list)
self._radio_layt.addWidget(radio)
self._radio_btns[label] = radio
self._radio_layt.addStretch()
self._layt.addLayout(self._radio_layt)
self._rev_list = QtGui.QListWidget()
self._layt.addWidget(self._rev_list)
label, rev_list = self._revs[0]
self._radio_btns[label].setChecked(True)
qtutils.set_items(self._rev_list, rev_list)
self.connect(self._rev_list, SIGNAL('itemSelectionChanged()'),
self._rev_list_selection_changed)
def revision(self):
return self._revision.text()
def set_revision_label(self, txt):
self._rev_label.setText(txt)
def _set_revision_list(self):
sender = str(self.sender().objectName())
revs = self._revdict[sender]
qtutils.set_items(self._rev_list, revs)
def _rev_list_selection_changed(self):
items = self._rev_list.selectedItems()
if not items:
return
self._revision.setText(items[0].text())
+66
View File
@@ -0,0 +1,66 @@
from PyQt4 import QtCore
from PyQt4 import QtGui
from PyQt4.QtCore import SIGNAL
from cola.views import standard
class ComboDialog(standard.Dialog):
"""A dialog for choosing branches."""
def __init__(self, parent=None, title='', items=None):
standard.Dialog.__init__(self, parent=parent)
self.setWindowTitle(title)
self.resize(400, 73)
self._main_layt = QtGui.QVBoxLayout(self)
# Exposed
self.items_widget = QtGui.QComboBox(self)
self.items_widget.setEditable(True)
self._main_layt.addWidget(self.items_widget)
self.button_box = QtGui.QDialogButtonBox(self)
self.button_box.setOrientation(QtCore.Qt.Horizontal)
self.button_box.setStandardButtons(QtGui.QDialogButtonBox.Ok |
QtGui.QDialogButtonBox.Cancel)
self._main_layt.addWidget(self.button_box)
self.setTabOrder(self.items_widget, self.button_box)
if items:
self.items_widget.addItems(items)
self.connect(self.button_box, SIGNAL('accepted()'), self.accept)
self.connect(self.button_box, SIGNAL('rejected()'), self.reject)
def idx(self):
return self.items_widget.currentIndex()
def value(self):
return unicode(self.items_widget.currentText())
def selected(self):
"""Present the dialog and return the chosen item."""
geom = QtGui.QApplication.instance().desktop().screenGeometry()
width = geom.width()
height = geom.height()
if self.parent():
x = self.parent().x() + self.parent().width()/2 - self.width()/2
y = self.parent().y() + self.parent().height()/3 - self.height()/2
self.move(x, y)
self.show()
if self.exec_() == QtGui.QDialog.Accepted:
return self.value()
else:
return None
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
combo = ComboDialog()
combo.show()
sys.exit(app.exec_())
+242
View File
@@ -0,0 +1,242 @@
from PyQt4 import QtGui
from PyQt4 import QtCore
from PyQt4.QtCore import Qt
from PyQt4.QtCore import SIGNAL
import cola
from cola import gitcmds
from cola import signals
from cola.qt import create_toolbutton
from cola.qtutils import apply_icon
from cola.qtutils import confirm
from cola.qtutils import connect_button
from cola.qtutils import emit
from cola.qtutils import log
from cola.qtutils import relay_signal
from cola.qtutils import save_icon
from cola.qtutils import tr
from cola.widgets import defs
from cola.prefs import diff_font
from cola.controllers.selectcommits import select_commits
from cola.dag.model import DAG
from cola.dag.model import RepoReader
class CommitMessageEditor(QtGui.QWidget):
def __init__(self, model, parent):
QtGui.QWidget.__init__(self, parent)
self.model = model
self._layt = QtGui.QVBoxLayout()
self._layt.setMargin(0)
self._layt.setSpacing(0)
self.commitmsg = CommitMessageTextEdit(model, self)
self.commitmsg.setFont(diff_font())
self._ctrls_layt = QtGui.QHBoxLayout()
self._ctrls_layt.setSpacing(defs.spacing)
self._ctrls_layt.setMargin(defs.margin)
# Sign off and commit buttons
self.signoff_button = create_toolbutton(self,
text='Sign Off',
tooltip='Sign off on this commit',
icon=apply_icon())
self.commit_button = create_toolbutton(self,
text='Commit@@verb',
tooltip='Commit staged changes',
icon=save_icon())
# Position display
self.position_label = QtGui.QLabel()
self.position_label.setAlignment(Qt.AlignCenter | Qt.AlignVCenter)
# Amend checkbox
self.amend_checkbox = QtGui.QCheckBox()
self.amend_checkbox.setText(tr('Amend Last Commit'))
self._ctrls_layt.addWidget(self.signoff_button)
self._ctrls_layt.addWidget(self.commit_button)
self._ctrls_layt.addWidget(self.position_label)
self._ctrls_layt.addStretch()
self._ctrls_layt.addWidget(self.amend_checkbox)
self._layt.addWidget(self.commitmsg)
self._layt.addLayout(self._ctrls_layt)
self.setLayout(self._layt)
relay_signal(self, self.commitmsg,
SIGNAL(signals.load_previous_message))
connect_button(self.commit_button, self.commit)
cola.notifier().connect(signals.amend, self.amend_checkbox.setChecked)
# Broadcast the amend mode
self.connect(self.amend_checkbox,
SIGNAL('toggled(bool)'), emit(self, signals.amend_mode))
self.connect(self.signoff_button,
SIGNAL('clicked()'), emit(self, signals.signoff))
# Display the current column
self.connect(self.commitmsg, SIGNAL('cursorPositionChanged()'),
self.show_cursor_position)
# Initialize the GUI to show 'Column: 00'
self.show_cursor_position()
def set_mode(self, mode):
checked = (mode == self.model.mode_amend)
self.amend_checkbox.blockSignals(True)
self.amend_checkbox.setChecked(checked)
self.amend_checkbox.blockSignals(False)
def commit(self):
"""Attempt to create a commit from the index and commit message."""
msg = unicode(self.commitmsg.toPlainText())
if not msg:
# Describe a good commit message
error_msg = 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')
log(1, error_msg)
cola.notifier().broadcast(signals.information,
'Missing Commit Message',
error_msg)
return
if not self.model.staged:
error_msg = tr(''
'No changes to commit.\n\n'
'You must stage at least 1 file before you can commit.')
if self.model.modified:
informative_text = tr('Would you like to stage and '
'commit all modified files?')
if not confirm('Stage and commit?',
error_msg,
informative_text,
'Stage and Commit',
default=False,
icon=save_icon()):
return
else:
cola.notifier().broadcast(signals.information,
'Nothing to commit',
error_msg)
return
cola.notifier().broadcast(signals.stage_modified)
# Warn that amending published commits is generally bad
amend = self.amend_checkbox.isChecked()
if (amend and self.model.is_commit_published() and
not confirm('Rewrite Published Commit?',
'This commit has already been published.\n'
'This operation will rewrite published history.\n'
'You probably don\'t want to do this.',
'Amend the published commit?',
'Amend Commit',
default=False, icon=save_icon())):
return
# Perform the commit
cola.notifier().broadcast(signals.commit, amend, msg)
def show_cursor_position(self):
"""Update the UI with the current row and column."""
cursor = self.commitmsg.textCursor()
position = cursor.position()
txt = unicode(self.commitmsg.toPlainText())
rows = txt[:position].count('\n') + 1
cols = cursor.columnNumber()
display = ' %d,%d ' % (rows, cols)
if cols > 78:
display = ('<span style="color: white; '
' background-color: red;"'
'>%s</span>' % display.replace(' ', '&nbsp;'))
elif cols > 72:
display = ('<span style="color: black; '
' background-color: orange;"'
'>%s</span>' % display.replace(' ', '&nbsp;'))
elif cols > 64:
display = ('<span style="color: black; '
' background-color: yellow;"'
'>%s</span>' % display.replace(' ', '&nbsp;'))
self.position_label.setText(display)
class CommitMessageTextEdit(QtGui.QTextEdit):
def __init__(self, model, parent):
QtGui.QTextEdit.__init__(self, parent)
self.model = model
self.notifying = False
self.menu_ready= False
self.setMinimumSize(QtCore.QSize(1, 1))
policy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum,
QtGui.QSizePolicy.Minimum)
self.setSizePolicy(policy)
self.setLineWrapMode(QtGui.QTextEdit.NoWrap)
self.setAcceptRichText(False)
self.model.add_message_observer(self.model.message_commit_message_changed,
self.set_commitmsg)
# Keep model informed of changes
self.connect(self, SIGNAL('textChanged()'), self.commitmsg_changed)
def commitmsg_changed(self):
self.notifying = True
self.model.set_commitmsg(unicode(self.toPlainText()))
self.notifying = False
def set_commitmsg(self, message):
if self.notifying:
return
self.blockSignals(True)
self.setPlainText(unicode(message))
self.blockSignals(False)
def contextMenuEvent(self, event):
self.menu_ready = False
menu = self.createStandardContextMenu()
menu.addSeparator()
prevmenu = menu.addMenu('Load Previous Commit Message')
self.connect(prevmenu, SIGNAL('aboutToShow()'),
lambda: self.prev_commit_menu_clicked(prevmenu, event))
menu.exec_(self.mapToGlobal(event.pos()))
self.menu_ready = False
def prev_commit_menu_clicked(self, menu, event):
if self.menu_ready:
return
dag = DAG('HEAD', 6)
commits = RepoReader(dag)
menu_commits = []
for idx, c in enumerate(commits):
menu_commits.insert(0, c)
if idx > 5:
continue
for c in menu_commits:
menu.addAction(c.subject,
lambda c=c: self.load_previous_message(c.sha1))
if len(commits) == 6:
menu.addSeparator()
menu.addAction('More...', self.choose_commit)
self.menu_ready = True
def choose_commit(self):
revs, summaries = gitcmds.log_helper()
sha1s = select_commits('Select Commit Message', revs, summaries,
multiselect=False)
if not sha1s:
return
sha1 = sha1s[0]
self.load_previous_message(sha1)
def load_previous_message(self, sha1):
self.emit(SIGNAL(signals.load_previous_message), sha1)

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