Compare commits

...
632 Commits
Author SHA1 Message Date
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
David Aguilar e0589d6fa8 Merged branchmenu into master
Squashed commit of the following:

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

    Creating branches works, but some options are not implemented

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

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

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

    UI updates: changed hotkeys

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

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

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

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

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

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

    Added copyright notices

    ugit is gplv2+.  Reflect that in the README.

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

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

    Use more screen real-estate

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

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

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

    Improved GitCreateBranch interaction behavior.

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

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

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

    Create branch WIP

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

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

    Removed superfluous *args from most callbacks

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

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

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

    Simplified GitCreateBranchController.__get_branch_sources()

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

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

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

    Updated GitCreateBranchController with user-interaction callbacks

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

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

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

    Added the first version of the GitCreateBranch dialog

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

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

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

    Moved the __set_branch_ui_items() call into cb_rescan

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

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

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

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

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

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

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

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

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

Squashed commit of the following:

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

    Implemented patch export in the repo browser

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

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

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

    Performance improvements for ansi_to_html

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

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

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

    Added tooltips and changed default window sizes

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

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

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

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

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

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

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

    Use git cat-file to show file contents

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

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

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

    Added "browse other branch" functionality

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

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

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

    Fixed a bug when changing directories

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

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

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

    Removed redundant label from CommandDialog

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

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

    Implemented the GitRepoBrowser

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

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

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

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

Signed-off by: David Aguilar <davvid@gmail.com>
2007-12-07 15:53:27 -08:00
David Aguilar 7a381c92d9 Renamed files in preparation for api changes
- We're going to put all ugit modules under ugit.$(module)
- This removes the need for api/ugitapi.py
- This also allows for nicer filenames
- The next commit updates the class names and adds __init__.py
2007-12-07 15:31:15 -08:00
135 changed files with 28818 additions and 2585 deletions
+21 -3
View File
@@ -1,4 +1,22 @@
/build
/install
/.lock-wscript
*.deb
*.pyc
*.pyo
*.rpm
*.swp
*~
/build
/build-stamp
/ChangeLog
/configure-stamp
/dist
/debian/*.log
/debian/files
/debian/bld
/debian/cola
/installroot
/.lock-wscript
/meta
/patches
/releases
/tags
/tmp
+1
View File
@@ -0,0 +1 @@
Copyright (C) 2008 David Aguilar (davvid@gmail.com) and contributors
+135
View File
@@ -0,0 +1,135 @@
INSTALLATION
------------
Debian and RPM users can download freshly built binaries here:
http://cola.tuxfamily.org/releases/
If you downloaded a tarball add /path/to/cola/bin to your $PATH.
REQUIREMENTS
Python 2.4 or newer
http://python.org/
apt-get install python
PyQt 4.3 or newer
http://www.riverbankcomputing.co.uk/software/pyqt/intro
apt-get install pyqt4-dev-tools
ADDITIONAL REQUIREMENTS (optional)
pyinotify (enables inotify support on Linux -- highly recommended)
http://pyinotify.sourceforge.net/
apt-get install python-pyinotify
simplejson (enables bookmarks)
http://pypi.python.org/pypi/simplejson
apt-get install python-simplejson
BUILDING FROM SOURCE
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 can also run git-cola without installing it.
It is designed to run directly out of the source tree
This is the best way to run git-cola when you're
modifying the code.
$ make
$ bin/git-cola
BUILD ISSUES
Some systems have pyuic4 scripts that are missing the shebang line.
If your version of $(wich pyuic4) looks like:
exec python /path/to/pyuic4.py ...
Then simply add this line to the top of the file:
#!/bin/sh
That will fix the build by making pyuic4 a proper shell script.
PYQT4 ISSUES
An older version of pyuic4 generated code that could not be
run by PyQt4. This problem has been fixed in the latest
versions of PyQt4 (4.4.x), but you might encounter it if
you are not running the latest version of PyQt4.
If you try to run git-cola and see error messages
about "setSortingEnabled(..)" then your pyuic4/PyQt4
version is affected. There's a simple workaround, though:
remove the line of code listed in the traceback and
everything should work fine.
PYUIC4 AND WINDOWS
To build cola on windows you'll need to add the following
shell script to a directory in your path and name it 'pyuic4':
#+-------------------------------------------------------------
#!/bin/sh
/c/Python25/python.exe \
/c/Python25/Lib/site-packages/PyQt4/uic/pyuic.py "$@"
#+-------------------------------------------------------------
WINDOWS INSTALLATION
1) Install the native Python2.5 and PyQt4 installers (in that order):
http://python.org/download/
http://www.riverbankcomputing.co.uk/pyqt/download.php
3) Install msysgit:
http://git.or.cz/gitwiki/WindowsInstall
4) Install the GnuWin32 'file' command:
http://gnuwin32.sourceforge.net/packages/file.htm
Add the installed directory (default: c:\GnuWin32\bin) to your path.
5) Install simplejson
http://pypi.python.org/pypi/simplejson
6) Download cola:
http://cola.tuxfamily.org/releases/
7) Install cola (from a Git Bash):
cd /c/Python25/Lib/site-packages
tar xvfz /path/to/cola-*-win32.tar.gz
mv cola-*-win32 cola
8) Create an executable in your path for bash, cmd.exe, or both:
echo '/c/Python25/python.exe /c/Python25/Lib/site-packages/cola/bin/git-cola' >> /usr/bin/cola
echo 'c:\\Python25\\python.exe c:\\Python25\\Lib\\site-packages\\cola\\bin\\git-cola' > /c/Windows/System32/cola.bat
This assumes the you have Python/PyQt4 installed in C:/Python25.
If this is not the case simply edit the commands accordingly.
voila, cola on Windows.
NOTE: Windows is not the developer's primary platform.
Patches are greatly appreciated.
+339
View File
@@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
+22
View File
@@ -0,0 +1,22 @@
include share/cola/qm/.gitignore
include share/cola/gui/.gitignore
include share/cola/views/*.py
include share/cola/po/*.po
include share/cola/icons/*.png
include share/cola/styles/*.qss
include share/cola/styles/images/*.png
include share/applications/*.desktop
include share/doc/cola/*.txt
include share/doc/cola/*.conf
include share/doc/cola/Makefile
include share/doc/cola/build-docdep.perl
include share/doc/cola/callouts.xsl
include ui/*.ui
include test/*.py
include test/Makefile
include README
include ChangeLog
include Makefile
include INSTALL
include LICENSE
include MANIFEST.in
+47
View File
@@ -0,0 +1,47 @@
prefix ?= $(HOME)
DESTDIR ?= /
PYTHON ?= python
all:
$(PYTHON) setup.py build
install:
$(PYTHON) setup.py install --prefix=$(prefix) --root=$(DESTDIR) --force
cd $(DESTDIR)$(prefix)/bin && rm -f cola && ln -s git-cola cola
doc:
cd share/doc/cola && $(MAKE) all
install-doc:
$(MAKE) -C share/doc/cola install
install-html:
$(MAKE) -C share/doc/cola install-html
uninstall:
rm -rf "$(DESTDIR)$(prefix)"/bin/git-cola \
"$(DESTDIR)$(prefix)"/bin/git-difftool \
"$(DESTDIR)$(prefix)"/bin/cola \
"$(DESTDIR)$(prefix)"/share/doc/cola \
"$(DESTDIR)$(prefix)"/share/cola \
"$(DESTDIR)$(prefix)"/share/applications/cola.desktop \
"$(DESTDIR)$(prefix)"/lib/python2.*/site-packages/cola \
"$(DESTDIR)$(prefix)"/lib/python2.*/site-packages/cola-*
test:
@env PYTHONPATH=$(CURDIR):$(CURDIR)/build/lib:$(PYTHONPATH) nosetests
clean:
for dir in share/doc/cola test; do \
(cd $$dir && $(MAKE) clean); \
done
find cola -name '*.py[co]' -print0 | xargs -0 rm -f
find cola/gui -name '[^_]*.py' -print0 | xargs -0 rm -f
find share -name '*.qm' -print0 | xargs -0 rm -f
rm -rf build tmp
rm -f tags
tags:
ctags -R cola/*.py cola/views/*.py cola/controllers/*.py
.PHONY: all install doc install-doc install-html test clean
+19 -184
View File
@@ -1,196 +1,31 @@
ugit: ugit's git-gui's inner truffles
git cola: a highly caffeinated git gui
ugit started out as a python/pyqt re-implementation of git-gui.
It has an evolving feature set and aims to be a fully-featured
companion to the existing git uis.
DEPENDENCIES:
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
python 2.4.x
pyqt 4.3.x
pyqtmvc 1.1.x
Cola is a sweet, carbonated git gui known for its
sugary flavour and caffeine-inspired features.
ABOUT THE ICONS:
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
Copyright (C) 2008 David Aguilar and contributors
staged.png was blatanly stolen from the "slick" KDE icon set.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
All other icons blatanly stolen from Apache's icons:
These icons are covered by the following copyright license:
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.
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
INSTALLATION
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
See the 'INSTALL' file for more details.
"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.
ACKNOWLEDGEMENTS
git cola includes a copy of the GitPython module:
http://gitorious.org/projects/git-python/
-4
View File
@@ -1,4 +0,0 @@
#!/usr/bin/env python
import os
import sys
sys.path.append (os.path.join (os.path.dirname (__file__), 'ugit'))
+6
View File
@@ -0,0 +1,6 @@
#!/bin/sh
# This should work if used with both the default Python2.5
# from python.org and the default PyQt4 binary package
# from Riverbank. Adjust accordingly.
DIR=$(dirname "$0")
c:/Python25/python.exe "$DIR"/git-cola
Executable
+54
View File
@@ -0,0 +1,54 @@
#!/usr/bin/env python
# -*- python-mode -*-
"""Takes care of starting the main function
"""
__copyright__ = """
Copyright (C) 2008, David Aguilar <davvid@gmail.com>
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, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
import os
import sys
# Put sys.path[0] in our path so that we can find our custom git-* commands
path_entries = os.getenv('PATH', '').split(os.pathsep)
if sys.path[0] not in path_entries:
path_entries.insert(0, sys.path[0])
path = os.pathsep.join(path_entries)
os.environ['PATH'] = path
# 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, bin = os.path.split(sys.path[0])
# Scripts is win32
if bin in ('bin', 'Scripts') and prefix != sys.prefix:
sys.prefix = prefix
sys.exec_prefix = prefix
major, minor = sys.version_info[0:2]
local_path = [os.path.join(prefix, 'lib', 'python%s.%s' % (major, minor),
'site-packages'),
os.path.join(prefix, 'Lib', 'site-packages'),
prefix,
os.getcwd(),
]
sys.path = local_path + sys.path
from cola.main import main
if __name__ == '__main__':
main()
+576
View File
@@ -0,0 +1,576 @@
#!/bin/sh
#
# This program uses merge tools to stage and compare commits
#
# Copyright (c) 2008 David Aguilar
#
# Adapted from git-mergetool.sh
# Copyright (c) 2006 Theodore Y. Ts'o
#
# This file is licensed under the GPL v2, or a later version
#
USAGE='
[--tool=tool] [--no-prompt]
[--commit=ref] [--start=ref --end=ref]
[--] [file to view] ...'
SUBDIRECTORY_OK=Yes
OPTIONS_SPEC=
. git-sh-setup
require_work_tree
cd_to_toplevel
keep_backup_mode="$(git config --bool merge.keepBackup || echo true)"
PREFIX=.git-difftool."$$"."$(date +%N)".
FILEDIR=
keep_backup () {
# whether to keep the .orig file
test "$keep_backup_mode" = "true"
}
parse_arg () {
# parses --foo=BAR
expr "z$1" : 'z-[^=]*=\(.*\)'
}
index_present () {
# does the index contain staged changes?
test -n "$index_mode"
}
modified_present () {
# are there changes in the requested comparison?
test -n "$modified_mode"
}
commitish_present () {
# are we comparing against an arbitrary commit?
test -n "$commitish"
}
should_prompt () {
# are we running interactively?
! test -n "$no_prompt"
}
use_rev_range () {
# are we using --start=REF and --end=REF
test -n "$start" && test -n "$end"
}
merge_dir_missing () {
# does dirname($MERGED) exist in the work tree?
test -n "$non_existant_dir"
}
base_present () {
# whether we have 3 things to compare (index, worktree, other)
index_present && modified_present && ! use_rev_range
}
modified_files () {
# returns the appopriate list of differences based on the mode
if use_rev_range; then
git diff --name-only "$start".."$end" -- "$@" 2>/dev/null
elif commitish_present; then
git diff --name-only "$commitish" -- "$@" 2>/dev/null
else
git diff --name-only -- "$@" 2>/dev/null
fi
}
staged_files() {
# returns any staged changes
git diff --name-only --cached "$@" 2>/dev/null
}
cleanup_temp_files () {
# removes temporary files
if test -n "$MERGED"; then
if keep_backup && test "$MERGED" -nt "$BACKUP"; then
test -f "$BACKUP" && mv -- "$BACKUP" "$MERGED.orig"
rm -f -- "$LOCAL" "$REMOTE" "$BASE"
else
rm -f -- "$LOCAL" "$REMOTE" "$BASE" "$BACKUP"
fi
test -n "$MERGEDIR" && rmdir -p "$MERGEDIR"
fi
}
sigint_handler () {
echo
cleanup_temp_files
exit 1
}
merge_file () {
# prepares temporary files and launches the appropriate merge tool
MERGED="$1"
modified_mode=$(modified_files "$MERGED")
index_mode=$(staged_files "$MERGED")
if ! modified_present && use_rev_range; then
echo "$MERGED: no changes between '$start' and '$end'."
exit 1
elif ! modified_present && ! index_present; then
if ! test -f "$MERGED"; then
echo "$MERGED: file not found"
else
echo "$MERGED: file is unchanged."
fi
exit 1
fi
# handle comparing a file that doesn't exist in the current checkout
MERGEDIR=$(dirname "$MERGED")
non_existant_dir=
test -d "$MERGEDIR" || non_existant_dir=true
if merge_dir_missing; then
mkdir -p -- "$MERGEDIR"
else
MERGEDIR=
fi
ext="$$$(expr "$MERGED" : '.*\(\.[^/]*\)$')"
BACKUP="./$MERGED.BACKUP.$ext"
test -f "$MERGED" && cp -- "$MERGED" "$BACKUP"
if use_rev_range; then
# we're comparing two arbitrary commits
BASE="./$MERGED.CURRENT.$ext"
LOCAL="./$MERGED.START.$ext"
REMOTE="./$MERGED.END.$ext"
base=current
local=start
remote=end
touch "$BASE"
touch "$LOCAL"
touch "$REMOTE"
# detects renames.. sweet!
oldname=$(git diff --follow "$start".."$end" -- "$MERGED" |
head -n9 |
grep '^rename from' |
sed -e 's/rename from //')
startname="$MERGED"
test -n "$oldname" && startname="$oldname"
if ! git show "$start":"$startname" > "$LOCAL" 2>/dev/null; then
if should_prompt; then
printf "\nWarning: "
printf "'$startname' does not exist at $start.\n"
fi
else
cp -- "$LOCAL" "$BASE"
fi
if ! git show "$end":"$MERGED" > "$REMOTE" 2>/dev/null; then
if should_prompt; then
printf "\nWarning: "
printf "'$MERGED' does not exist at $end.\n"
fi
else
! test -f "$BASE" && cp -- "$REMOTE" "$BASE"
fi
# $BASE could be used by custom mergetool commands
if test -f "$MERGED"; then
cp -- "$MERGED" "$BASE"
fi
else
# We're either comparing against the index or an
# arbitrary commit.
# The goal is to re-use existing mergetool.$tool.cmd
# configurations so we provide $BASE $LOCAL and $REMOTE
if commitish_present; then
HEAD=OTHER
local=Other
else
HEAD=HEAD
local=Index
fi
base=${commitish-HEAD}
remote=Current
BASE="./$MERGED.$HEAD.$ext"
LOCAL="./$MERGED.INDEX.$ext"
REMOTE="./$MERGED.CURRENT.$ext"
touch "$BASE"
touch "$LOCAL"
touch "$REMOTE"
if ! git show "$base":"$MERGED" > "$BASE" 2>&1; then
printf "\nWarning: "
printf "'$MERGED' does not exist at $base.\n"
else
cp "$BASE" "$LOCAL"
if commitish_present; then
rm -f "$LOCAL"
LOCAL="$BASE"
fi
fi
# If changes are present in the index use them as $LOCAL
# but only if $MERGED exists at $base
if ! commitish_present; then
git checkout-index --prefix="$PREFIX" "$MERGED"
if test -f "$PREFIX""$MERGED"; then
mv -- "$PREFIX""$MERGED" "$LOCAL"
tmpdir=$(dirname "$PREFIX""$MERGED")
test -d "$tmpdir" &&
test "$tmpdir" != "." &&
rmdir -p "$tmpdir"
else
index_mode=
fi
else
index_mode=
fi
if test -f "$MERGED"; then
cp -- "$MERGED" "$REMOTE"
fi
fi
# ensure that we clean up after ourselves
trap sigint_handler SIGINT
if should_prompt; then
printf "\nEditing: '$MERGED'\n"
printf "Hit return to launch '%s': " "$merge_tool"
read ans
fi
case "$merge_tool" in
kdiff3)
basename=$(basename "$MERGED")
if base_present; then
(
"$merge_tool_path" --auto \
--L1 "($base) $basename" \
--L2 "($local) $basename" \
--L3 "($remote) $basename" \
-o "$MERGED" "$BASE" "$LOCAL" "$REMOTE" \
> /dev/null 2>&1
)
else
(
"$merge_tool_path" --auto \
--L1 "($local) $basename" \
--L2 "($remote) $basename" \
-o "$MERGED" "$LOCAL" "$REMOTE" \
> /dev/null 2>&1
)
fi
;;
tkdiff)
if base_present; then
"$merge_tool_path" \
-a "$BASE" \
-o "$MERGED" "$LOCAL" "$REMOTE"
else
"$merge_tool_path" \
-o "$MERGED" "$LOCAL" "$REMOTE"
fi
;;
meld)
"$merge_tool_path" "$LOCAL" "$MERGED"
;;
vimdiff)
if base_present; then
"$merge_tool_path" "$BASE" "$LOCAL" "$MERGED"
else
"$merge_tool_path" "$LOCAL" "$MERGED"
fi
;;
gvimdiff)
if base_present; then
"$merge_tool_path" -f "$BASE" "$LOCAL" "$MERGED"
else
"$merge_tool_path" -f "$LOCAL" "$MERGED"
fi
;;
xxdiff)
if base_present; then
"$merge_tool_path" -X --show-merged-pane \
-R 'Accel.SaveAsMerged: "Ctrl-S"' \
-R 'Accel.Search: "Ctrl+F"' \
-R 'Accel.SearchForward: "Ctrl-G"' \
--merged-file "$MERGED" \
"$BASE" "$LOCAL" "$REMOTE"
else
"$merge_tool_path" -X --show-merged-pane \
-R 'Accel.SaveAsMerged: "Ctrl-S"' \
-R 'Accel.Search: "Ctrl+F"' \
-R 'Accel.SearchForward: "Ctrl-G"' \
--merged-file "$MERGED" \
"$LOCAL" "$REMOTE"
fi
;;
opendiff)
if base_present; then
"$merge_tool_path" "$LOCAL" "$REMOTE" \
-ancestor "$BASE" \
-merge "$MERGED" | cat
else
"$merge_tool_path" "$LOCAL" "$REMOTE" \
-merge "$MERGED" | cat
fi
;;
ecmerge)
if base_present; then
"$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" \
--default --mode=merge3 --to="$MERGED"
else
"$merge_tool_path" "$LOCAL" "$REMOTE" \
--default --mode=merge2 --to="$MERGED"
fi
;;
emerge)
if base_present; then
"$merge_tool_path" \
-f emerge-files-with-ancestor-command \
"$LOCAL" "$REMOTE" "$BASE" \
"$(basename "$MERGED")"
else
"$merge_tool_path" -f emerge-files-command \
"$LOCAL" "$REMOTE" "$(basename "$MERGED")"
fi
;;
*)
if test -n "$merge_tool_cmd"; then
if test "$merge_tool_trust_exit_code" = "false"; then
( eval $merge_tool_cmd )
else
( eval $merge_tool_cmd )
fi
fi
;;
esac
cleanup_temp_files
}
while test $# != 0
do
case "$1" in
-t|--tool*)
case "$#,$1" in
*,*=*)
merge_tool=$(parse_arg "$1")
shift
;;
1,*)
usage
;;
*)
shift
merge_tool="$1"
shift
;;
esac
;;
-c|--commit*)
case "$#,$1" in
*,*=*)
commitish=$(parse_arg "$1")
shift
;;
1,*)
usage
;;
*)
shift
commitish="$1"
shift
;;
esac
;;
-s|--start*)
case "$#,$1" in
*,*=*)
start=$(parse_arg "$1")
shift
;;
1,*)
usage
;;
*)
shift
start="$1"
shift
;;
esac
;;
-e|--end*)
case "$#,$1" in
*,*=*)
end=$(parse_arg "$1")
shift
;;
1,*)
usage
;;
*)
shift
end="$1"
shift
;;
esac
;;
--no-prompt)
no_prompt=true
shift
;;
--)
shift
break
;;
-*)
usage
;;
*)
break
;;
esac
done
valid_custom_tool() {
merge_tool_cmd="$(git config mergetool.$1.cmd)"
test -n "$merge_tool_cmd"
}
valid_tool() {
case "$1" in
kdiff3 | tkdiff | xxdiff | meld | opendiff | emerge | vimdiff | gvimdiff | ecmerge)
;; # happy
*)
if ! valid_custom_tool "$1"
then
return 1
fi
;;
esac
}
init_merge_tool_path() {
merge_tool_path=$(git config mergetool."$1".path)
if test -z "$merge_tool_path"; then
case "$1" in
emerge)
merge_tool_path=emacs
;;
*)
merge_tool_path="$1"
;;
esac
fi
}
if test -z "$merge_tool"; then
merge_tool=$(git config merge.tool)
if test -n "$merge_tool" && ! valid_tool "$merge_tool"; then
echo >&2 "git config option merge.tool set to unknown tool: $merge_tool"
echo >&2 "Resetting to default..."
unset merge_tool
fi
fi
if test -z "$merge_tool"; then
if test -n "$DISPLAY"; then
merge_tool_candidates="kdiff3 tkdiff xxdiff meld gvimdiff"
if test -n "$GNOME_DESKTOP_SESSION_ID"; then
merge_tool_candidates="meld $merge_tool_candidates"
fi
if test "$KDE_FULL_SESSION" = "true"; then
merge_tool_candidates="kdiff3 $merge_tool_candidates"
fi
fi
if echo "${VISUAL:-$EDITOR}" | grep 'emacs' > /dev/null 2>&1; then
merge_tool_candidates="$merge_tool_candidates emerge"
fi
if echo "${VISUAL:-$EDITOR}" | grep 'vim' > /dev/null 2>&1; then
merge_tool_candidates="$merge_tool_candidates vimdiff"
fi
merge_tool_candidates="$merge_tool_candidates opendiff emerge vimdiff"
echo "merge tool candidates: $merge_tool_candidates"
for i in $merge_tool_candidates
do
init_merge_tool_path $i
if type "$merge_tool_path" > /dev/null 2>&1; then
merge_tool=$i
break
fi
done
if test -z "$merge_tool" ; then
echo "No known merge resolution program available."
exit 1
fi
else
if ! valid_tool "$merge_tool"; then
echo >&2 "Unknown merge tool $merge_tool"
exit 1
fi
init_merge_tool_path "$merge_tool"
if test -z "$merge_tool_cmd" && ! type "$merge_tool_path" > /dev/null 2>&1; then
echo "The merge tool $merge_tool is not available as '$merge_tool_path'"
exit 1
fi
if ! test -z "$merge_tool_cmd"; then
merge_tool_trust_exit_code="$(git config --bool mergetool.$merge_tool.trustExitCode || echo false)"
fi
fi
if test $# -eq 0; then
use_index=0
files=$(modified_files)
if test -z "$files"; then
use_index=1
files=$(staged_files)
fi
if test -z "$files"; then
echo "No modified files exist."
exit 1
fi
if test $use_index -eq 0; then
modified_files |
while IFS= read i
do
merge_file "$i" < /dev/tty > /dev/tty
done
elif ! use_rev_range; then
staged_files |
while IFS= read i
do
merge_file "$i" < /dev/tty > /dev/tty
done
else
echo "Nothing to compare."
exit 1
fi
else
while test $# -gt 0
do
merge_file "$1"
shift
done
fi
exit 0
+1
View File
@@ -0,0 +1 @@
@python py2exe-setup.py py2exe
+15
View File
@@ -0,0 +1,15 @@
from distutils.core import setup
import py2exe
setup(
windows = [
{
"script" : "git-cola",
},
],
options = {
"py2exe" : {
"includes" : ["PyQt4._qt", "pprint", "sip"],
}
}
)
-14
View File
@@ -1,14 +0,0 @@
#!/usr/bin/env python
import sys
from PyQt4 import QtCore, QtGui
import ugitapi
from ugitmodel import GitModel
from ugitview import GitView
from ugitcontroller import GitController
if __name__ == "__main__":
app = QtGui.QApplication (sys.argv)
model = GitModel()
view = GitView (app.activeWindow())
ctl = GitController (model, view)
view.show()
sys.exit(app.exec_())
View File
File diff suppressed because it is too large Load Diff
+53
View File
@@ -0,0 +1,53 @@
#!/usr/bin/env python
"""This controller handles the bookmarks dialog."""
import os
import sys
from PyQt4 import QtGui
from cola import utils
from cola import qtutils
from cola.qobserver import QObserver
from cola.settings import SettingsModel
from cola.views import BookmarkView
def save_bookmark():
model = SettingsModel()
bookmark = os.getcwd()
if bookmark not in model.bookmarks:
model.add_bookmarks(bookmark)
model.save_all_settings()
qtutils.information("Bookmark Saved")
def manage_bookmarks():
model = SettingsModel()
view = BookmarkView(QtGui.qApp.activeWindow())
ctl = BookmarkController(model, view)
view.show()
if view.exec_() == QtGui.QDialog.Accepted:
model.save_all_settings()
class BookmarkController(QObserver):
def init(self, model, view):
self.add_observables( 'bookmarks' )
self.add_callbacks(button_open = self.open,
button_delete = self.delete)
self.refresh_view()
def open(self):
selection = qtutils.get_selection_list(self.view.bookmarks,
self.model.bookmarks)
if not selection:
return
for item in selection:
utils.fork("git", "cola", item)
def delete(self):
selection = qtutils.get_selection_list(self.view.bookmarks,
self.model.bookmarks)
if not selection:
return
for item in selection:
self.model.bookmarks.remove(item)
self.refresh_view()
+310
View File
@@ -0,0 +1,310 @@
"""This controller handles the compare commits dialog."""
import os
from cola import utils
from cola import qtutils
from cola.qobserver import QObserver
from cola.views import CompareView
from cola.views import BranchCompareView
from cola.controllers.repobrowser import select_file_from_repo
from cola.controllers.util import choose_from_list
def compare(model, parent):
model = model.clone()
model.create(descriptions_start=[], descriptions_end=[],
revisions_start=[], revisions_end=[],
revision_start='', revision_end='',
compare_files=[], num_results=100,
show_versions=False)
view = CompareView(parent)
ctl = CompareController(model, view)
view.show()
def branch_compare(model, parent):
model = model.clone()
model.create(left_combo=['Local', 'Remote'],
right_combo=['Local', 'Remote'],
left_combo_index=0,
right_combo_index=1,
left_list=[],
right_list=[],
left_list_index=-1,
right_list_index=-1,
left_list_selected=False,
right_list_selected=False,
diff_files=[])
view = BranchCompareView(parent)
ctl = BranchCompareController(model, view)
view.show()
class BranchCompareController(QObserver):
BRANCH_POINT = '*** Branch Point ***'
SANDBOX = '*** Sandbox ***'
def init(self, model, view):
self.add_observables('left_combo', 'right_combo',
'left_list', 'right_list',
'diff_files')
self.add_callbacks(button_compare =
self.diff_files_doubleclick,
left_combo =
self.gen_update_combo_boxes(left=True),
right_combo =
self.gen_update_combo_boxes(left=False),
left_list =
self.update_diff_files,
right_list =
self.update_diff_files)
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):
if (not self.model.has_param('left_list_item') or
not self.model.has_param('right_list_item')):
return
left_item = self.model.get_left_list_item()
right_item = self.model.get_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.get_remote_ref(left_item)
right_item = self.get_remote_ref(right_item)
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
self.start = left_item
self.end = right_item
files = self.model.get_diff_filenames(self.diff_arg)
self.model.set_diff_files(files)
icon = qtutils.get_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 get_diff_arg(self, start, end):
if start == BranchCompareController.SANDBOX:
return end
elif end == BranchCompareController.SANDBOX:
return start
else:
return '%s..%s' % (start, end)
def get_remote_ref(self, branch):
if branch == BranchCompareController.BRANCH_POINT:
# Compare against the branch point so find the merge-base
branch = self.model.get_currentbranch()
remote = self.model.get_corresponding_remote_ref()
return self.model.git.merge_base(branch, remote)
else:
# Compare against the remote branch
return branch
def gen_update_combo_boxes(self, left=False):
"""Returns a closure which modifies the listwidgets based on the
combobox selection.
"""
def update_combo_boxes(notused):
if left:
which = self.model.get_left_combo_item()
param = 'left_list'
else:
which = self.model.get_right_combo_item()
param = 'right_list'
if not which:
return
if which == 'Local':
new_list = ([BranchCompareController.SANDBOX]+
self.model.get_local_branches())
else:
new_list = ([BranchCompareController.BRANCH_POINT] +
self.model.get_remote_branches())
# Update the list widget
self.model.set_notify(True)
self.model.set_param(param, new_list)
return update_combo_boxes
def diff_files_doubleclick(self):
tree_widget = self.view.diff_files
id_num, selected = qtutils.get_selected_treeitem(tree_widget)
if not selected:
qtutils.information('Oops!', 'Please select a file to compare')
return
filename = self.model.get_diff_files()[id_num]
self.__compare_file(filename)
def __compare_file(self, filename):
git = self.model.git
if self.use_sandbox:
kwargs = git.transform_kwargs(no_prompt=True,
tool=self.model.get_mergetool(),
commit=self.diff_arg)
else:
kwargs = git.transform_kwargs(no_prompt=True,
tool=self.model.get_mergetool(),
start=self.start,
end=self.end)
args = (['git', 'difftool'] + kwargs + ['--', filename])
utils.fork(*args)
class CompareController(QObserver):
"""Drives the Commit->Compare Commits dialog.
"""
def init (self, model, view):
self.add_observables('descriptions_start', 'descriptions_end',
'revision_start', 'revision_end',
'compare_files', 'num_results',
'show_versions')
self.add_actions(num_results = self.update_results)
self.add_actions(show_versions = self.update_results)
self.add_callbacks(descriptions_start =
self.gen_update_widgets(True),
descriptions_end =
self.gen_update_widgets(False),
button_compare =
self.compare_selected_file)
self.refresh_view()
revisions = self.update_results()
last = len(revisions)
# if we can, preselect the latest commit
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 get_distance_from_end(self, tree_widget):
item_id, item_selected = qtutils.get_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):
tree_widget = self.view.descriptions_start
start_selected, start_delta = self.get_distance_from_end(tree_widget)
tree_widget = self.view.descriptions_end
end_selected, end_delta = self.get_distance_from_end(tree_widget)
self.model.set_notify(True)
show_versions = self.model.get_show_versions()
revs = self.model.update_revision_lists(show_versions=show_versions)
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 gen_update_widgets(self, left=True):
def update(*args):
self.update_widgets(left=left)
return update
def update_widgets(self, left=True):
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'
id_num, selected = qtutils.get_selected_treeitem(tree_widget)
if not selected:
return
revision = self.model.get_param(revisions_param)[id_num]
self.model.set_param(revision_param, revision)
# get the changed files list
start = self.model.get_revision_start()
end = self.model.get_revision_end()
files = self.model.get_changed_files(start, end)
# get the old name of any renamed files, and prune them
# from the changes list
renamed_files = self.model.get_renamed_files(start, end)
for renamed in renamed_files:
try:
files.remove(renamed)
except:
pass
self.model.set_compare_files(files)
icon = qtutils.get_icon('script.png')
for idx in xrange(0, self.view.compare_files.topLevelItemCount()):
item = self.view.compare_files.topLevelItem(idx)
item.setIcon(0, icon)
qtutils.set_clipboard(self.model.get_param(revision_param))
def compare_selected_file(self):
tree_widget = self.view.compare_files
id_num, selected = qtutils.get_selected_treeitem(tree_widget)
if not selected:
qtutils.information('Oops!', 'Please select a file to compare')
return
filename = self.model.get_compare_files()[id_num]
self.__compare_file(filename)
def compare_files_doubleclick(self, tree_item, column):
idx = self.view.compare_files.indexOfTopLevelItem(tree_item)
filename = self.model.get_compare_files()[idx]
self.__compare_file(filename)
def __compare_file(self, filename):
git = self.model.git
start = self.model.get_revision_start()
end = self.model.get_revision_end()
kwargs = git.transform_kwargs(no_prompt=True,
tool=self.model.get_mergetool(),
start=start,
end=end)
args = (['git', 'difftool'] + kwargs + ['--', filename])
utils.fork(*args)
+135
View File
@@ -0,0 +1,135 @@
#!/usr/bin/env python
"""This controller handles the create branch dialog."""
import os
from PyQt4.QtGui import QDialog
from cola import utils
from cola import qtutils
from cola.views import CreateBranchView
from cola.qobserver import QObserver
def create_new_branch(model,parent):
model = model.clone()
view = CreateBranchView(parent)
ctl = CreateBranchController(model, view)
view.show()
return view.exec_() == QDialog.Accepted
class CreateBranchController(QObserver):
def init(self, model, view):
self.add_observables('revision', 'local_branch')
self.add_callbacks(branch_list = self.item_changed,
create_button = self.create_branch,
local_radio = self.display_model,
remote_radio = self.display_model,
tag_radio = self.display_model)
self.display_model()
#+--------------------------------------------------------------------
#+ Qt callbacks
def create_branch(self):
"""This callback is called when the "Create Branch"
button is called."""
revision = self.model.get_revision()
branch = self.model.get_local_branch()
existing_branches = self.model.get_local_branches()
if not branch or not revision:
qtutils.information(
self.tr('Missing Data'),
self.tr('Please provide both a branch'
+' name and revision expression.'))
return
check_branch = False
if branch in existing_branches:
if self.view.no_update_radio.isChecked():
msg = self.tr("Branch '%s' already exists.")
msg = unicode(msg) % branch
qtutils.information( self.tr('warning'), msg )
return
# Whether we should prompt the user for lost commits
commits = self.model.rev_list_range(revision, branch)
check_branch = bool(commits)
if check_branch:
msg = self.tr("Resetting '%s' to '%s' will "
"lose the following commits:")
lines = [ unicode(msg) % (branch, revision) ]
for idx, commit in enumerate(commits):
subject = commit[1][0:min(len(commit[1]),16)]
if len(subject) < len(commit[1]):
subject += '...'
lines.append('\t' + commit[0][:8]
+'\t' + subject)
if idx >= 5:
skip = len(commits) - 5
lines.append('\t(%d skipped)' % skip)
break
lines.extend([
unicode(self.tr('Recovering lost commits may not be easy.')),
unicode(self.tr("Reset '%s'?")) % branch
])
result = qtutils.question(self.view, self.tr('warning'),
'\n'.join(lines))
if not result:
return
track = self.view.remote_radio.isChecked()
fetch = self.view.fetch_checkbox.isChecked()
ffwd = self.view.ffwd_only_radio.isChecked()
reset = self.view.reset_radio.isChecked()
chkout = self.view.checkout_checkbox.isChecked()
output = self.model.create_branch(branch, revision, track=track)
qtutils.show_output(output)
if chkout:
status, out, err = self.model.git.checkout(branch,
with_extended_output=True)
qtutils.show_output(out+err)
self.view.accept()
def item_changed(self, *rest):
"""This callback is called when the item selection changes
in the branch_list."""
qlist = self.view.branch_list
( row, selected ) = qtutils.get_selected_row(qlist)
if not selected: return
sources = self.__get_branch_sources()
rev = sources[row]
# Update the model with the selection
self.model.set_revision(rev)
# Only set the branch name field if we're
# branching from a remote branch.
if not self.view.remote_radio.isChecked():
return
branch = utils.basename(rev)
if branch == 'HEAD':
return
self.model.set_local_branch(branch)
######################################################################
def display_model(self):
branches = self.__get_branch_sources()
qtutils.set_items(self.view.branch_list, branches)
def __get_branch_sources(self):
"""Get the list of items for populating the branch root list."""
if self.view.local_radio.isChecked():
return self.model.get_local_branches()
elif self.view.remote_radio.isChecked():
return self.model.get_remote_branches()
elif self.view.tag_radio.isChecked():
return self.model.get_tags()
+103
View File
@@ -0,0 +1,103 @@
#!/usr/bin/env python
"""This controller handles the merge dialog."""
from PyQt4.Qt import Qt
from cola import utils
from cola import qtutils
from cola.qobserver import QObserver
from cola.views import MergeView
def abort_merge(model, parent):
txt = parent.tr('Abort merge?\n'
'Aborting the current merge will cause '
'*ALL* uncommitted changes to be lost.\n\n'
'Continue with aborting the current merge?')
answer = qtutils.question(parent, parent.tr('Abort Merge?'), txt)
if answer:
model.abort_merge()
def local_merge(model, parent):
model = model.clone()
model.create(revision='', revision_list=[])
view = MergeView(parent)
ctl = MergeController(model, view)
view.show()
class MergeController(QObserver):
def init(self, model, view):
# Set the current branch label
branch = self.model.get_currentbranch()
title = unicode(self.tr('Merge Into %s')) % branch
self.view.label.setText(title)
self.add_observables('revision', 'revision_list')
self.add_callbacks(radio_local = self.radio_callback,
radio_remote = self.radio_callback,
radio_tag = self.radio_callback,
revision_list = self.revision_selected,
button_viz = self.viz_revision,
button_merge = self.merge_revision,
checkbox_squash = self.squash_update)
self.model.set_revision_list(self.model.get_local_branches())
self.view.radio_local.setChecked(True)
def get_revision_list(self):
if self.view.radio_local.isChecked():
return self.model.get_local_branches()
elif self.view.radio_remote.isChecked():
return self.model.get_remote_branches()
elif self.view.radio_tag.isChecked():
return self.model.get_tags()
return []
def revision_selected(self, *args):
revlist = self.get_revision_list()
widget = self.view.revision_list
row, selected = qtutils.get_selected_row(widget)
if selected and row < len(revlist):
revision = revlist[row]
self.model.set_revision(revision)
def radio_callback(self):
revlist = self.get_revision_list()
self.model.set_revision_list(revlist)
def merge_revision(self):
revision = self.model.get_revision()
if not revision:
qtutils.information('No Revision Specified',
'You must specify a revision to merge')
return
no_commit = not(self.view.checkbox_commit.isChecked())
squash = self.view.checkbox_squash.isChecked()
msg = self.model.get_merge_message()
qtutils.log(self.model.git.merge('-m'+msg,
revision,
strategy='recursive',
no_commit=no_commit,
squash=squash),
quiet=False,
doraise=True)
self.view.accept()
def viz_revision(self):
revision = self.model.get_revision()
browser = self.model.get_history_browser()
utils.fork(browser, revision)
def squash_update(self):
if self.view.checkbox_squash.isChecked():
self.old_commit_checkbox_state =\
self.view.checkbox_commit.checkState()
self.view.checkbox_commit.setCheckState(Qt.Unchecked)
self.view.checkbox_commit.setDisabled(True)
else:
self.view.checkbox_commit.setDisabled(False)
try:
oldstate = self.old_commit_checkbox_state
self.view.checkbox_commit.setCheckState(oldstate)
except AttributeError:
# no problem
pass
+129
View File
@@ -0,0 +1,129 @@
"""This controller handles the remote dialog."""
import os
from PyQt4.QtGui import QDialog
from cola import utils
from cola import qtutils
from cola.views import RemoteView
from cola.qobserver import QObserver
def remote_action(model, parent, action):
model = model.clone()
model.create(remotename='',
tags_checkbox=False,
ffwd_only_checkbox=True)
view = RemoteView(parent, action)
if action == 'Fetch' or action == 'Pull':
model.set_tags_checkbox(False)
if action == 'Pull':
view.tags_checkbox.hide()
controller = RemoteController(model, view, action)
view.show()
return view.exec_() == QDialog.Accepted
class RemoteController(QObserver):
def init(self, model, view, action):
self.add_observables('remotename',
'remotes',
'local_branch',
'local_branches',
'remote_branch',
'remote_branches',
'tags_checkbox',
'ffwd_only_checkbox')
self.action_method = {
'Fetch': self.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.add_actions(remotes = self.display_remotes)
self.add_callbacks(action_button = self.action_method,
remotes = self.update_remotes,
local_branches = self.update_local_branches,
remote_branches = self.update_remote_branches)
self.refresh_view()
if self.view.select_first_remote():
self.model.set_remotename(self.model.get_remotes()[0])
def display_remotes(self, widget):
displayed = []
for remotename in self.model.get_remotes():
url = self.model.remote_url(remotename)
display = ('%s\t(%s %s)'
% (remotename, unicode(self.tr('URL:')), url))
displayed.append(display)
qtutils.set_items(widget,displayed)
def update_remotes(self,*rest):
widget = self.view.remotes
remotes = self.model.get_remotes()
selection = qtutils.get_selected_item(widget, remotes)
if not selection:
return
self.model.set_remotename(selection)
self.view.remotename.selectAll()
def update_local_branches(self,*rest):
branches = self.model.get_local_branches()
widget = self.view.local_branches
selection = qtutils.get_selected_item(widget, branches)
if not selection:
return
self.model.set_local_branch(selection)
self.model.set_remote_branch(selection)
self.view.local_branch.selectAll()
self.view.remote_branch.selectAll()
def update_remote_branches(self,*rest):
widget = self.view.remote_branches
branches = self.model.get_remote_branches()
selection = qtutils.get_selected_item(widget,branches)
if not selection:
return
branch = utils.basename(selection)
if branch == 'HEAD':
return
self.model.set_remote_branch(branch)
self.view.remote_branch.selectAll()
def check_remote(self):
if not self.model.get_remotename():
errmsg = self.tr('No repository selected.')
qtutils.show_output(errmsg)
return False
else:
return True
def get_common_args(self):
return (self.model.get_remotename(),
{
'local_branch': self.model.get_local_branch(),
'remote_branch': self.model.get_remote_branch(),
'ffwd': self.model.get_ffwd_only_checkbox(),
'tags': self.model.get_tags_checkbox(),
})
def show_results(self, output):
qtutils.show_output(output)
self.view.accept()
qtutils.raise_logger()
#+-------------------------------------------------------------
#+ Actions
def gen_remote_callback(self, modelaction):
"""Generates a Qt callback for fetch/push/pull.
"""
def remote_callback():
if not self.check_remote():
return
remote, kwargs = self.get_common_args()
output = modelaction(remote, **kwargs)
if not output: # git fetch --tags --verbose doesn't print anything...
output = self.tr('Already up-to-date.')
self.show_results(output)
return remote_callback
+180
View File
@@ -0,0 +1,180 @@
#!/usr/bin/env python
"""This controller handles the repository file browser."""
import os
from PyQt4.QtGui import QDialog
from cola import utils
from cola import qtutils
from cola.views import CommitView
from cola.qobserver import QObserver
def select_file_from_repo(model, parent):
model = model.clone()
view = CommitView(parent)
controller = RepoBrowserController(model, view,
title='Select File',
get_file=True)
view.show()
if view.exec_() == QDialog.Accepted:
return controller.filename
else:
return None
def browse_git_branch(model, parent, branch):
if not branch:
return
# Clone the model to allow opening multiple browsers
# with different sets of data
model = model.clone()
model.set_currentbranch(branch)
view = CommitView(parent)
controller = RepoBrowserController(model, view)
view.show()
return view.exec_() == QDialog.Accepted
class RepoBrowserController(QObserver):
def init(self, model, view, title='File Browser', get_file=False):
self.get_file = get_file
self.filename = None
view.setWindowTitle(title)
self.add_signals('itemSelectionChanged()', view.commit_list,)
self.add_actions(directory = self.action_directory_changed)
self.add_callbacks(commit_list = self.item_changed)
# Start at the root of the tree
model.set_directory('')
######################################################################
# Actions
def action_directory_changed(self):
"""This is called in response to a change in the the
model's directory."""
self.model.init_browser_data()
self.__display_items()
######################################################################
# Qt callbacks
def item_changed(self,*rest):
"""This is called when the current item changes in the
file/directory list(aka the commit_list)."""
current = self.view.commit_list.currentRow()
item = self.view.commit_list.item(current)
if item is None or not item.isSelected():
self.view.revision.setText('')
self.view.commit_text.setText('')
return
directories = self.model.get_directories()
directory_entries = self.model.get_directory_entries()
if current < len(directories):
# This is a directory...
self.filename = None
dirent = directories[current]
if dirent != '..':
# This is a real directory for which
# we have child entries
entries = directory_entries[dirent]
else:
# This is '..' which is a special case
# since it doesn't really exist
entries = []
self.view.commit_text.setText('\n'.join(entries))
self.view.revision.setText('')
else:
# This is a file entry. The current row is absolute,
# so get a relative index by subtracting the number
# of directory entries
idx = current - len(directories)
if idx >= len(self.model.get_subtree_sha1s()):
# This can happen when changing directories
self.filename = None
return
objtype, sha1, name = self.model.get_subtree_node(idx)
curdir = self.model.get_directory()
if curdir:
self.filename = os.path.join(curdir, name)
else:
self.filename = name
catguts = self.model.git.cat_file(objtype, sha1,
with_raw_output=True)
self.view.commit_text.setText(catguts)
self.view.revision.setText(sha1)
self.view.revision.selectAll()
# Copy the sha1 into the clipboard
qtutils.set_clipboard(sha1)
# automatically called by qobserver
def commit_list_doubleclick(self,*rest):
"""This is called when an entry is double-clicked.
This callback changes the model's directory when
invoked on a directory item. When invoked on a file
it allows the file to be saved."""
current = self.view.commit_list.currentRow()
directories = self.model.get_directories()
# A file item was double-clicked.
# Create a save-as dialog and export the file,
# or if in get_file mode, grab the filename and finish the dialog.
if current >= len(directories):
idx = current - len(directories)
objtype, sha1, name = self.model.get_subtree_node(idx)
if self.get_file:
if self.model.get_directory():
curdir = self.model.get_directory()
self.filename = os.path.join(curdir, name)
else:
self.filename = name
self.view.accept()
return
nameguess = os.path.join(self.model.get_directory(), name)
filename = qtutils.save_dialog(self.view, 'Save', nameguess)
if not filename:
return
self.model.set_directory(os.path.dirname(filename))
contents = self.model.git.cat_file(objtype, sha1,
with_raw_output=True)
utils.write(filename, contents)
return
dirent = directories[current]
curdir = self.model.get_directory()
# '..' is a special case--it doesn't really exist...
if dirent == '..':
newdir = os.path.dirname(os.path.dirname(curdir))
if newdir == '':
self.model.set_directory(newdir)
else:
self.model.set_directory(newdir + os.sep)
else:
self.model.set_directory(curdir + dirent)
######################################################################
def __display_items(self):
"""This method populates the commit_list(aka item list)
with the current directories and items. Directories are
always listed first."""
self.view.commit_text.setText('')
self.view.revision.setText('')
dir_icon = utils.get_icon('dir.png')
file_icon = utils.get_icon('generic.png')
creator = qtutils.create_listwidget_item
qtutils.set_items(self.view.commit_list,
map(lambda d: creator(d, dir_icon),
self.model.get_directories()))
qtutils.add_items(self.view.commit_list,
map(lambda s: creator(s, file_icon),
self.model.get_subtree_names()))
+254
View File
@@ -0,0 +1,254 @@
#!/usr/bin/env python
"""This controller handles the search dialog."""
import os
import re
import time
from PyQt4 import QtGui
from cola.observer import Observer
from cola.qobserver import QObserver
from cola.views import SearchView
from cola import qtutils
class SearchEngine(object):
def __init__(self, model):
self.model = model
self.init()
def init(self):
pass
def get_rev_args(self):
max = self.model.get_max_results()
return { "max-count": max, "pretty": "oneline" }
def get_common_args(self):
return (self.model.get_input(), self.get_rev_args())
def search(self):
if not self.validate():
return
return self.get_results()
def validate(self):
return len(self.model.get_input()) > 1
def get_revisions(self, *args, **kwargs):
revlist = self.model.git.rev_list(*args, **kwargs)
return self.model.parse_rev_list(revlist)
def get_results(self):
pass
class RevisionSearch(SearchEngine):
def get_results(self):
input, args = self.get_common_args()
expr = re.compile(input)
revs = self.get_revisions(all=True, **args)
return [ r for r in revs if expr.match(r[0]) ]
class RevisionRangeSearch(SearchEngine):
def init(self):
self.RE = re.compile(r'[^.]*\.\..*')
def validate(self):
return bool(self.RE.match(self.model.get_input()))
def get_results(self):
input, kwargs = self.get_common_args()
return self.get_revisions(input, **kwargs)
class PathSearch(SearchEngine):
def get_results(self):
input, args = self.get_common_args()
paths = ['--'] + input.split(':')
return self.get_revisions(all=True, *paths, **args)
class MessageSearch(SearchEngine):
def get_results(self):
input, kwargs = self.get_common_args()
return self.get_revisions(all=True, grep=input, **kwargs)
class AuthorSearch(SearchEngine):
def get_results(self):
input, kwargs = self.get_common_args()
return self.get_revisions(all=True, author=input, **kwargs)
class CommitterSearch(SearchEngine):
def get_results(self):
input, kwargs = self.get_common_args()
return self.get_revisions(all=True, committer=input, **kwargs)
class DiffSearch(SearchEngine):
def get_results(self):
input, kwargs = self.get_common_args()
return self.model.parse_rev_list(
self.model.git.log('-S'+input, all=True, **kwargs))
class DateRangeSearch(SearchEngine):
def validate(self):
return True
def get_results(self):
kwargs = self.get_rev_args()
start_date = self.model.get_start_date()
end_date = self.model.get_end_date()
return self.get_revisions(date='iso',
all=True,
after=start_date,
before=end_date,
**kwargs)
# Modes for this controller.
# Note: names correspond to radio button names for convenience
REVISION_ID = 'radio_revision'
REVISION_RANGE = 'radio_range'
PATH = 'radio_path'
MESSAGE = 'radio_message'
DIFF = 'radio_diff'
AUTHOR = 'radio_author'
COMMITTER = 'radio_committer'
DATE_RANGE = 'radio_daterange'
# Each search type is handled by a distinct SearchEngine subclass
SEARCH_ENGINES = {
REVISION_ID: RevisionSearch,
REVISION_RANGE: RevisionRangeSearch,
PATH: PathSearch,
MESSAGE: MessageSearch,
DIFF: DiffSearch,
AUTHOR: AuthorSearch,
COMMITTER: CommitterSearch,
DATE_RANGE: DateRangeSearch,
}
class SearchController(QObserver):
def init(self, model, view):
self.add_observables('input',
'max_results',
'start_date',
'end_date')
self.add_actions(max_results = self.search_callback,
start_date = self.search_callback,
end_date = self.search_callback)
self.add_callbacks(
# Standard buttons
button_search = self.search_callback,
button_browse = self.browse_callback,
commit_list = self.display_callback,
button_export = self.export_patch,
button_cherrypick = self.cherry_pick,
# Radio buttons trigger a search
radio_revision = self.search_callback,
radio_range = self.search_callback,
radio_message = self.search_callback,
radio_path = self.search_callback,
radio_diff = self.search_callback,
radio_author = self.search_callback,
radio_committer = self.search_callback,
radio_daterange = self.search_callback,
)
self.update_fonts()
def update_fonts(self):
font = self.model.get_cola_config('fontui')
if font:
qfont = QtGui.QFont()
qfont.fromString(font)
self.view.commit_list.setFont(qfont)
font = self.model.get_cola_config('fontdiff')
if font:
qfont = QtGui.QFont()
qfont.fromString(font)
self.view.commit_text.setFont(qfont)
def set_mode(self, mode):
radio = getattr(self.view, mode)
radio.setChecked(True)
def radio_to_mode(self, radio_button):
return str(radio_button.objectName())
def get_mode(self):
for name in SEARCH_ENGINES:
radiobutton = getattr(self.view, name)
if radiobutton.isChecked():
return name
def search_callback(self, *args):
engineclass = SEARCH_ENGINES.get(self.get_mode())
if not engineclass:
print ("mode: '%s' is currently unimplemented"
% self.get_mode())
return
self.results = engineclass(self.model).search()
if self.results:
self.display_results()
else:
self.view.commit_list.clear()
self.view.commit_text.setText('')
def browse_callback(self):
paths = QtGui.QFileDialog.getOpenFileNames(self.view,
self.tr("Choose Path(s)"))
if not paths:
return
filepaths = []
lenprefix = len(os.getcwd()) + 1
for path in map(lambda x: unicode(x), paths):
if not path.startswith(os.getcwd()):
continue
filepaths.append(path[lenprefix:])
input = ':'.join(filepaths)
self.model.set_input('')
self.set_mode(PATH)
self.model.set_input(input)
def display_results(self):
commit_list = map(lambda x: x[1], self.results)
self.model.set_commit_list(commit_list)
qtutils.set_listwidget_strings(self.view.commit_list, commit_list)
def display_callback(self, *args):
widget = self.view.commit_list
row, selected = qtutils.get_selected_row(widget)
if not selected or len(self.results) < row:
return
revision = self.results[row][0]
qtutils.set_clipboard(revision)
diff = self.model.get_commit_diff(revision)
self.view.commit_text.setText(diff)
def export_patch(self):
widget = self.view.commit_list
row, selected = qtutils.get_selected_row(widget)
if not selected or len(self.results) < row:
return
revision = self.results[row][0]
qtutils.log(self.model.export_patchset(revision, revision),
doraise=True,
quiet=False)
def cherry_pick(self):
widget = self.view.commit_list
row, selected = qtutils.get_selected_row(widget)
if not selected or len(self.results) < row:
return
revision = self.results[row][0]
qtutils.log(self.model.git.cherry_pick(revision),
doraise=True,
quiet=False)
def search_commits(model, mode, browse):
def get_date(timespec):
return '%04d-%02d-%02d' % time.localtime(timespec)[:3]
model = model.clone()
model.create(input='',
max_results=500,
start_date='',
end_date='',
commit_list=None)
view = SearchView(None)
ctl = SearchController(model, view)
ctl.set_mode(mode)
model.set_start_date(get_date(time.time()-(87640*7)))
model.set_end_date(get_date(time.time()+87640))
view.show()
if browse:
ctl.browse_callback()
view.exec_()
+115
View File
@@ -0,0 +1,115 @@
"""This controller handles the stash dialog."""
import os
from cola import utils
from cola import qtutils
from cola.qobserver import QObserver
from cola.views import StashView
def stash(model, parent):
model = model.clone()
model.create( stash_list=[], stash_revids=[] )
view = StashView(parent)
ctl = StashController(model, view)
view.show()
class StashController(QObserver):
def init (self, model, view):
self.add_observables('stash_list')
self.add_callbacks(button_stash_show = self.stash_show,
button_stash_apply = self.stash_apply,
button_stash_drop = self.stash_drop,
button_stash_clear = self.stash_clear,
button_stash_save = self.stash_save)
self.update_model()
def update_model(self):
self.model.set_stash_list(self.model.parse_stash_list())
self.model.set_stash_revids(self.model.parse_stash_list(revids=True))
self.refresh_view()
def get_selected_stash(self):
list_widget = self.view.stash_list
stash_list = self.model.get_stash_revids()
return qtutils.get_selected_item(list_widget, stash_list)
def stash_save(self):
if not qtutils.question(self.view,
self.tr('Stash Changes?'),
self.tr('This will stash your current '
'changes away for later use.\n'
'Continue?')):
return
stash_name, ok = qtutils.input(self.tr('Enter a name for this stash'))
if not ok:
return
while stash_name in self.model.get_stash_list():
qtutils.information(self.tr("Oops!"),
self.tr('That name already exists. '
'Please enter another name.'))
stash_name, ok = qtutils.input(self.tr("Enter a name for this stash"))
if not ok:
return
if not stash_name:
return
# Sanitize our input, just in case
stash_name = utils.sanitize_input(stash_name)
qtutils.log(self.model.git.stash('save', stash_name),
quiet=False,
doraise=True)
self.view.accept()
def stash_show(self):
"""Shows the current stash in the main view."""
selection = self.get_selected_stash()
if not selection:
return
diffstat = self.model.git.stash('show', selection)
diff = self.model.stash('show', '-p', selection)
self.view.parent_view.display('%s\n\n%s' % (diffstat, diff))
def stash_apply(self):
selection = self.get_selected_stash()
if not selection:
return
(status, stdout, stderr) =\
self.model.git.stash('apply', selection,
with_extended_output=True)
qtutils.log(stdout + stderr,
quiet=False,
doraise=True)
self.view.accept()
def stash_drop(self):
selection = self.get_selected_stash()
if not selection:
return
if not qtutils.question(self.view,
self.tr('Drop Stash?'),
self.tr('This will permanently remove the '
'selected stash.\n'
'Recovering these changes may not '
'be possible.\n\n'
'Continue?')):
return
qtutils.log(self.model.git.stash('drop', selection),
quiet=False,
doraise=True)
self.update_model()
def stash_clear(self):
if not qtutils.question(self.view,
self.tr('Drop All Stashes?'),
self.tr('This will permanently remove '
'ALL stashed changes.\n'
'Recovering these changes may not '
'be possible.\n\n'
'Continue?')):
return
self.model.git.stash('clear'),
self.update_model()
+306
View File
@@ -0,0 +1,306 @@
#!/usr/bin/env python
"""This module provides utility controllers.
"""
import os
import time
from PyQt4.QtGui import QDialog
from PyQt4.QtGui import QFont
from cola import utils
from cola import qtutils
from cola.model import Model
from cola.views import ListView
from cola.views import ComboView
from cola.views import CommitView
from cola.views import OptionsView
from cola.views import LogView
from cola.qobserver import QObserver
def set_diff_font(model, widget):
if model.has_param('global_cola_fontdiff'):
font = model.get_param('global_cola_fontdiff')
if not font:
return
qf = QFont()
qf.fromString(font)
widget.setFont(qf)
def choose_from_combo(title, parent, items):
return ComboView(parent,
title=title,
items=items).get_selected()
def choose_from_list(title, parent, items=[], dblclick=None):
return ListView(parent,
title=title,
items=items,
dblclick=dblclick).get_selected()
#+-------------------------------------------------------------
def select_commits(model, parent, title, revs, summaries):
"""Use the CommitView to select commits from a list."""
model = model.clone()
model.set_revisions(revs)
model.set_summaries(summaries)
view = CommitView(parent, title)
ctl = SelectCommitsController(model, view)
return ctl.select_commits()
class SelectCommitsController(QObserver):
def init(self, model, view):
set_diff_font(model, view.commit_text)
self.connect(view.commit_list,
'itemSelectionChanged()',
self.commit_sha1_selected)
def select_commits(self):
summaries = self.model.get_summaries()
if not summaries:
msg = self.tr('No commits exist in this branch.')
qtutils.show_output(msg)
return []
qtutils.set_items(self.view.commit_list, summaries)
self.view.show()
if self.view.exec_() != QDialog.Accepted:
return []
revs = self.model.get_revisions()
list_widget = self.view.commit_list
return qtutils.get_selection_list(list_widget, revs)
def commit_sha1_selected(self):
row, selected = qtutils.get_selected_row(self.view.commit_list)
if not selected:
self.view.commit_text.setText('')
self.view.revision.setText('')
return
# Get the sha1 and put it in the revision line
sha1 = self.model.get_revision_sha1(row)
self.view.revision.setText(sha1)
self.view.revision.selectAll()
# Lookup the sha1's commit
commit_diff = self.model.get_commit_diff(sha1)
self.view.commit_text.setText(commit_diff)
# Copy the sha1 into the clipboard
qtutils.set_clipboard(sha1)
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#+ The Options GUI Controller
def update_options(model, parent):
view = OptionsView(parent)
ctl = OptionsController(model,view)
view.show()
return view.exec_() == QDialog.Accepted
class OptionsController(QObserver):
"""ASSUMPTIONS:
This controller assumes that the view's widgets are named
the same as the model parameters."""
def init(self, model, view):
# used for telling about interactive font changes
self.original_model = model
model = model.clone()
self.add_observables('local_user_email',
'local_user_name',
'local_merge_summary',
'local_merge_diffstat',
'local_merge_verbosity',
'local_gui_diffcontext',
'global_user_email',
'global_user_name',
'global_merge_keepbackup',
'global_merge_summary',
'global_merge_diffstat',
'global_merge_verbosity',
'global_gui_editor',
'global_merge_tool',
'global_gui_diffcontext',
'global_gui_historybrowser',
'global_cola_fontdiffsize',
'global_cola_fontdiff',
'global_cola_fontuisize',
'global_cola_fontui',
'global_cola_savewindowsettings',
)
self.add_actions(global_cola_fontdiffsize = self.update_size)
self.add_actions(global_cola_fontuisize = self.update_size)
self.add_actions(global_cola_fontdiff = self.tell_parent_model)
self.add_actions(global_cola_fontui = self.tell_parent_model)
self.add_callbacks(save_button = self.save_settings)
self.connect(self.view, 'rejected()', self.restore_settings)
self.refresh_view()
self.backup_model = self.model.clone()
def refresh_view(self):
font = self.model.get_cola_config('fontui')
if font:
size = int(font.split(',')[1])
self.view.global_cola_fontuisize.setValue(size)
self.model.set_global_cola_fontuisize(size)
fontui = QFont()
fontui.fromString(font)
self.view.global_cola_fontui.setCurrentFont(fontui)
font = self.model.get_cola_config('fontdiff')
if font:
size = int(font.split(',')[1])
self.view.global_cola_fontdiffsize.setValue(size)
self.model.set_global_cola_fontdiffsize(size)
fontdiff = QFont()
fontdiff.fromString(font)
self.view.global_cola_fontdiff.setCurrentFont(fontdiff)
self.view.local_groupbox.setTitle(unicode(self.tr('%s Repository'))
% self.model.get_project())
QObserver.refresh_view(self)
# save button
def save_settings(self):
params_to_save = []
params = self.model.get_config_params()
for param in params:
value = self.model.get_param(param)
backup = self.backup_model.get_param(param)
if value != backup:
params_to_save.append(param)
for param in params_to_save:
self.model.save_config_param(param)
self.original_model.copy_params(self.model, params_to_save)
self.view.done(QDialog.Accepted)
# cancel button -> undo changes
def restore_settings(self):
params = self.backup_model.get_config_params()
self.model.copy_params(self.backup_model, params)
self.tell_parent_model()
def tell_parent_model(self,*rest):
params= ('global_cola_fontdiff',
'global_cola_fontui',
'global_cola_fontdiffsize',
'global_cola_fontuisize',
'global_cola_savewindowsettings',
)
for param in params:
self.original_model.set_param(
param, self.model.get_param(param))
def update_size(self, *rest):
combo = self.view.global_cola_fontui
param = str(combo.objectName())
default = str(combo.currentFont().toString())
self.model.apply_font_size(param, default)
combo = self.view.global_cola_fontdiff
param = str(combo.objectName())
default = str(combo.currentFont().toString())
self.model.apply_font_size(param, default)
self.tell_parent_model()
#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#+ The Log GUI Controller
def logger():
model = Model(search_text = '')
view = LogView(None)
ctl = LogController(model,view)
return view
class LogController(QObserver):
def init(self, model, view):
self.add_observables('search_text')
self.add_actions(search_text = self.insta_search)
self.add_callbacks(clear_button = self.clear,
next_button = self.next,
prev_button = self.prev)
self.connect(self.view.output_text,
'cursorPositionChanged()',
self.cursor_position_changed)
self.search_offset = 0
def insta_search(self,*rest):
self.search_offset = 0
txt = self.model.get_search_text().lower()
if len(txt.strip()):
self.next()
else:
cursor = self.view.output_text.textCursor()
cursor.clearSelection()
self.view.output_text.setTextCursor(cursor)
def clear(self):
self.view.output_text.clear()
self.search_offset = 0
def next(self):
text = self.model.get_search_text().lower().strip()
if not text:
return
output = str(self.view.output_text.toPlainText())
if self.search_offset + len(text) > len(output):
title = unicode(self.tr('%s not found')) % text
question = unicode(self.tr("Could not find '%s'.\n"
'Search from the beginning?')) % text
if qtutils.question(self.view, title, question, default=False):
self.search_offset = 0
else:
return
find_in = output[self.search_offset:].lower()
try:
index = find_in.index(text)
except:
self.search_offset = 0
title = unicode(self.tr("%s not found")) % text
question = unicode(self.tr("Could not find '%s'.\n"
'Search from the beginning?')) % text
if qtutils.question(self.view, title, default=False):
self.next()
return
cursor = self.view.output_text.textCursor()
offset = self.search_offset + index
new_offset = offset + len(text)
cursor.setPosition(offset)
cursor.setPosition(new_offset, cursor.KeepAnchor)
self.view.output_text.setTextCursor(cursor)
self.search_offset = new_offset
def prev(self):
text = self.model.get_search_text().lower().strip()
if not text:
return
output = str(self.view.output_text.toPlainText())
if self.search_offset == 0:
self.search_offset = len(output)
find_in = output[:self.search_offset].lower()
try:
offset = find_in.rindex(text)
except:
self.search_offset = 0
title = unicode(self.tr('%s not found')) % text
question = unicode(self.tr("Could not find '%s'.\n"
'Search from the end?')) % text
if qtutils.question(self.view, title, question):
self.prev()
return
cursor = self.view.output_text.textCursor()
new_offset = offset + len(text)
cursor.setPosition(offset)
cursor.setPosition(new_offset, cursor.KeepAnchor)
self.view.output_text.setTextCursor(cursor)
self.search_offset = offset
def cursor_position_changed(self):
cursor = self.view.output_text.textCursor()
self.search_offset = cursor.selectionStart()
+27
View File
@@ -0,0 +1,27 @@
"""This module provides unicode encode and decode utilities.
"""
# 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 = [
"utf8",
"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
"""
# FIXME is utf-8 the right thing here?
return unenc.encode('utf-8', 'replace')
+4
View File
@@ -0,0 +1,4 @@
# Copyright (c) 2008 David Aguilar
class ColaException(Exception):
"""Base class for all Cola exceptions."""
pass
+248
View File
@@ -0,0 +1,248 @@
# cmd.py
# Copyright (C) 2008 Michael Trier (mtrier@gmail.com) and contributors
#
# This module is part of GitPython and is released under
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
import re
import os
import sys
import subprocess
import errno
from cola.core import encode
class GitCommandError(Exception):
"""Exception class for failed commands."""
pass
def dashify(string):
return string.replace('_', '-')
# Enables debugging of GitPython's git commands
GIT_PYTHON_TRACE = os.environ.get("GIT_PYTHON_TRACE", False)
execute_kwargs = ('istream', 'with_keep_cwd', 'with_extended_output',
'with_exceptions', 'with_raw_output')
class Git(object):
"""
The Git class manages communication with the Git binary
"""
def __init__(self):
"""Constructs a Git command object."""
self._git_cwd = None #: The working directory used by execute()
def set_cwd(self, path):
"""Sets the current directory."""
self._git_cwd = path
def __getattr__(self, name):
"""Handles the self.git_command(..) dispatching."""
if name[0] == '_':
raise AttributeError(name)
return lambda *args, **kwargs: self._call_process(name, *args, **kwargs)
@staticmethod
def execute(command,
cwd=None,
istream=None,
with_keep_cwd=False,
with_extended_output=False,
with_exceptions=False,
with_raw_output=False):
"""
Handles executing the command on the shell and consumes and returns
the returned information (stdout)
``command``
The command argument list to execute
``istream``
Standard input filehandle passed to subprocess.Popen.
``with_keep_cwd``
Whether to use the current working directory from os.getcwd().
GitPython uses the cwd set by set_cwd() by default.
``with_extended_output``
Whether to return a (status, stdout, stderr) tuple.
``with_exceptions``
Whether to raise an exception when git returns a non-zero status.
``with_raw_output``
Whether to avoid stripping off trailing whitespace.
Returns
str(output) # extended_output = False (Default)
tuple(int(status), str(output)) # extended_output = True
"""
if GIT_PYTHON_TRACE and not GIT_PYTHON_TRACE == 'full':
print ' '.join(command)
# Allow the user to have the command executed in their working dir.
if with_keep_cwd or not cwd:
cwd = os.getcwd()
# Start the process
use_shell = sys.platform in ('win32')
if use_shell and sys.platform == 'darwin':
command = shell_quote(*command)
proc = subprocess.Popen(command,
cwd=cwd,
shell=use_shell,
stdin=istream,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE)
while True:
try:
stdout_value = proc.stdout.read()
stderr_value = proc.stderr.read()
status = proc.wait()
break
except IOError, e:
# 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
if e.errno == errno.EINTR:
continue
else:
stdout_value = None
stderr_value = None
status = 42
break
if with_exceptions and status:
raise GitCommandError(command, status, stderr_value)
if not stdout_value:
stdout_value = ''
if not stderr_value:
stderr_value = ''
if not with_raw_output:
stdout_value = stdout_value.strip()
stderr_value = stderr_value.strip()
if GIT_PYTHON_TRACE == 'full':
if stderr_value:
print "%s -> %d: '%s' !! '%s'" % (command, status, stdout_value, stderr_value)
elif stdout_value:
print "%s -> %d: '%s'" % (command, status, stdout_value)
else:
print "%s -> %d" % (command, status)
# Allow access to the command's status code
if with_extended_output:
return (status, stdout_value, stderr_value)
else:
if stdout_value and stderr_value:
return stderr_value + '\n' + stdout_value
elif stdout_value:
return stdout_value
else:
return stderr_value
def transform_kwargs(self, **kwargs):
"""
Transforms Python style kwargs into git command line options.
"""
args = []
for k, v in kwargs.items():
if len(k) == 1:
if v is True:
args.append("-%s" % k)
elif type(v) is not bool:
args.append("-%s%s" % (k, v))
else:
if v is True:
args.append("--%s" % dashify(k))
elif type(v) is not bool:
args.append("--%s=%s" % (dashify(k), v))
return args
def _call_process(self, method, *args, **kwargs):
"""
Run the given git command with the specified arguments and return
the result as a String
``method``
is the command
``args``
is the list of arguments
``kwargs``
is a dict of keyword arguments.
This function accepts the same optional keyword arguments
as execute().
Examples
git.rev_list('master', max_count=10, header=True)
Returns
Same as execute()
"""
# Handle optional arguments prior to calling transform_kwargs
# otherwise these'll end up in args, which is bad.
_kwargs = dict(cwd=self._git_cwd)
for kwarg in execute_kwargs:
try:
_kwargs[kwarg] = kwargs.pop(kwarg)
except KeyError:
pass
# Prepare the argument list
opt_args = self.transform_kwargs(**kwargs)
ext_args = map(encode, args)
args = opt_args + ext_args
call = ['git', dashify(method)]
call.extend(args)
return Git.execute(call, **_kwargs)
def shell_quote(*inputs):
"""
Quote strings so that they can be suitably martialled
off to the shell. This method supports POSIX sh syntax.
This is crucial to properly handle command line arguments
with spaces, quotes, double-quotes, etc. on darwin/win32...
"""
regex = re.compile('[^\w!%+,\-./:@^]')
quote_regex = re.compile("((?:'\\''){2,})")
ret = []
for input in inputs:
if not input:
continue
if '\x00' in input:
raise AssertionError,('No way to quote strings '
'containing null(\\000) bytes')
# = does need quoting else in command position it's a
# program-local environment setting
match = regex.search(input)
if match and '=' not in input:
# ' -> '\''
input = input.replace("'", "'\\''")
# make multiple ' in a row look simpler
# '\'''\'''\'' -> '"'''"'
quote_match = quote_regex.match(input)
if quote_match:
quotes = match.group(1)
input.replace(quotes, ("'" *(len(quotes)/4)) + "\"'")
input = "'%s'" % input
if input.startswith("''"):
input = input[2:]
if input.endswith("''"):
input = input[:-2]
ret.append(input)
return ' '.join(ret)
+1
View File
@@ -0,0 +1 @@
/[^_]*.py
View File
+89
View File
@@ -0,0 +1,89 @@
#!/usr/bin/env python
# Copyright (c) 2008 David Aguilar
"""This module provides an inotify plugin for Linux and other systems
which provide the pyinotify module.
"""
import os
import time
from PyQt4.QtCore import QCoreApplication
from PyQt4.QtCore import QThread
from PyQt4.QtCore import QEvent
from PyQt4.QtCore import SIGNAL
from pyinotify import ProcessEvent
from pyinotify import WatchManager, Notifier, EventsCodes
INOTIFY_EVENT_TYPE = QEvent.User + 0
class FileSysEvent(ProcessEvent):
"""FileSysEvents are generated by the GitNotifier thread
and represent the inotify events.
"""
def __init__(self, parent):
"""FileSysEvent keeps a reference to the Qt parent and remembers
the last event time to avoid event floods.
"""
ProcessEvent.__init__(self)
self.parent = parent
self.last_event_time = time.time()
def process_default(self, event):
"""Notifies the Qt parent when actions occur."""
# Prevent notificaiton floods
if time.time() - self.last_event_time > 1.0:
self.parent.notify()
self.last_event_time = time.time()
class GitNotifier(QThread):
"""This polls inotify for changes and generates FileSysEvents
in response to updates.
"""
def __init__(self, receiver, git):
QThread.__init__(self)
self.receiver = receiver #: The Qt receiver of the event
self.git = git # A: git command object
self.path = git.get_work_tree() #: Path to monitor
self.abort = False #: Whether to abort (using during destruction)
self.dirs_seen = {} #: Directories we're watching
#: Events we capture
self.mask = (EventsCodes.IN_CREATE | EventsCodes.IN_DELETE |
EventsCodes.IN_MODIFY | EventsCodes.IN_MOVED_TO)
def notify(self):
"""Posts a Qt event in response to inotify updates.
"""
if not self.abort:
event_type = QEvent.Type(INOTIFY_EVENT_TYPE)
event = QEvent(event_type)
QCoreApplication.postEvent(self.receiver, event)
def watch_directory(self, directory):
"""Sets up a directory for monitoring by inotify.
"""
directory = os.path.realpath(directory)
if directory not in self.dirs_seen:
self.wm.add_watch(directory, self.mask)
self.dirs_seen[directory] = True
def run(self):
"""This creates the inotify WatchManager and generates
FileSysEvents."""
# Only capture those events that git cares about
self.wm = WatchManager()
notifier = Notifier(self.wm, FileSysEvent(self))
dirs_seen = {}
added_flag = False
# self.abort signals app termination. The timeout is a tradeoff
# between fast notification response and waiting too long to exit.
while not self.abort:
if not added_flag:
self.watch_directory(self.path)
# Register files/directories known to git
for file in self.git.ls_files().splitlines():
directory = os.path.dirname(file)
self.watch_directory(directory)
added_flag = True
notifier.process_events()
if notifier.check_events(timeout=250):
notifier.read_events()
notifier.stop()
+129
View File
@@ -0,0 +1,129 @@
# Copyright(C) 2008, David Aguilar <davvid@gmail.com>
"""This module provides the main() routine used by the
git-cola launcher script.
"""
import optparse
import sys
import os
from cola import utils
def main():
"""Parses the command-line arguments and starts git-cola.
"""
parser = optparse.OptionParser(usage='%prog [options]')
parser.add_option('-v', '--version',
help='Show cola version',
dest='version',
default=False,
action='store_true')
parser.add_option('-s', '--style',
help='Applies an alternate stylesheet. '
'The allowed values are: "dark" or a file path.',
dest='style',
metavar='PATH or STYLE',
default='')
parser.add_option('-r', '--repo',
help='Specifies the path to a git repository.',
dest='repo',
metavar='PATH',
default=os.getcwd())
opts, args = parser.parse_args()
if opts.version or 'version' in args:
from cola import git
git.Git.execute(['git', 'update-index', '--refresh'])
from cola import version
print "cola version", version.version
sys.exit(0)
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)
os.chdir(opts.repo)
try:
# Defer these imports to allow git cola --version without pyqt installed
from PyQt4 import QtCore
from PyQt4 import QtGui
except ImportError:
print >> sys.stderr, 'Sorry, you do not seem to have PyQt4 installed.'
print >> sys.stderr, 'Please install it before using cola.'
print >> sys.stderr, 'e.g.: sudo apt-get install python-qt4'
sys.exit(-1)
from cola import qtutils
class ColaApplication(QtGui.QApplication):
"""This makes translation work by throwing out the context."""
wrapped = QtGui.QApplication.translate
def translate(*args):
trtxt = unicode(ColaApplication.wrapped('', *args[2:]))
if trtxt[-6:-4] == '@@': # handle @@verb / @@noun
trtxt = trtxt[:-6]
return trtxt
# Initialize the ap
app = ColaApplication(sys.argv)
app.setWindowIcon(QtGui.QIcon(utils.get_icon('git.png')))
# Handle i18n -- load translation files and install a translator
QtGui.QApplication.translate = app.translate
locale = str(QtCore.QLocale().system().name())
qmfile = utils.get_qm_for_locale(locale)
if os.path.exists(qmfile):
translator = QtCore.QTranslator(app)
translator.load(qmfile)
app.installTranslator(translator)
if opts.style:
# This loads the built-in and user-specified stylesheets.
# We allows absolute and relative paths to a stylesheet
# by assuming that non-file arguments refer to a built-in style.
if os.path.isabs(opts.style) or os.path.isfile(opts.style):
filename = opts.style
else:
filename = utils.get_stylesheet(opts.style)
try:
# Automatically register each subdirectory in the style dir
# as a Qt resource directory.
dirname = os.path.dirname(filename)
resource_paths = []
resource_paths.extend(utils.get_resource_dirs(dirname))
for r in resource_paths:
basename = os.path.basename(r)
QtCore.QDir.setSearchPaths(basename, [r])
stylesheet = open(filename, 'r')
style = stylesheet.read()
stylesheet.close()
app.setStyleSheet(style)
except:
print >> sys.stderr, ("warn: '%s' is not a valid style."
% opts.style)
# Initialize the model/view/controller framework
from cola.models import Model
from cola.views import View
from cola.controllers import Controller
model = Model()
view = View(app.activeWindow())
# Ensure that we're working in a valid git repository.
# If not, try to find one. When found, chdir there.
valid = model.use_worktree(repo)
while not valid:
gitdir = qtutils.opendir_dialog(view, 'Open Git Repository...', os.getcwd())
if not gitdir:
sys.exit(-1)
valid = model.use_worktree(gitdir)
os.chdir(model.git.get_work_tree())
# Show the GUI and start the event loop
view.show()
ctl = Controller(model, view)
sys.exit(app.exec_())
+408
View File
@@ -0,0 +1,408 @@
#!/usr/bin/env python
# Copyright (c) 2008 David Aguilar
"""This module provides the Model class, an observable and serializable
data model.
"""
import os
import imp
from cStringIO import StringIO
from types import DictType
from types import ListType
from types import TupleType
from types import StringTypes
from types import BooleanType
from types import IntType
from types import LongType
from types import FloatType
from types import ComplexType
from types import InstanceType
from types import FunctionType
class Observable(object):
"""Handles subject/observer notifications."""
def __init__(self,*args,**kwargs):
self.__observers = []
self.__notify = True
def get_notify(self):
return self.__notify
def set_notify(self, notify=True):
self.__notify = notify
def add_observer(self, observer):
if observer not in self.__observers:
self.__observers.append(observer)
def remove_observer(self, observer):
if observer in self.__observers:
self.__observers.remove(observer)
def notify_observers(self, *param):
if not self.__notify: return
for observer in self.__observers:
observer.notify(*param)
class ModelIterator(object):
"""Provides an iterator over model (key, value) pairs.
"""
def __init__(self, model):
self.model = model
self.params = model.get_param_names()
self.idx = -1
def next(self):
try:
self.idx += 1
name = self.params[self.idx]
return (name, self.model[name])
except IndexError:
raise StopIteration
class Model(Observable):
"""Creates a generic model object with params specified
as a name:value dictionary.
get_name() and set_name(value) are created automatically
for any of the parameters specified in the **kwargs"""
def __init__(self, *args, **kwargs):
Observable.__init__(self)
self.from_dict(kwargs)
self.init()
def init(self):
"""init() is called by the built-in constructor.
Subclasses should implement this if necessary."""
pass
def __getitem__(self, item):
return self.__dict__[item]
def __iter__(self):
return ModelIterator(self)
def items(self):
d = self.to_dict()
d.pop('__class__', None)
return d.items()
def iteritems(self):
d = self.to_dict()
d.pop('__class__', None)
return d.iteritems()
def create(self,**kwargs):
return self.from_dict(kwargs)
def get_param_names(self):
"""Returns a list of serializable attribute names."""
names = []
for k, v in self.__dict__.iteritems():
if k[0] == '_' or is_function(v):
continue
if is_atom(v) or is_list(v) or is_dict(v) or is_model(v):
names.append(k)
names.sort()
return names
def notify_all(self):
self.notify_observers(*self.get_param_names())
def clone(self, *args, **kwargs):
return self.__class__(*args, **kwargs).from_dict(self.to_dict())
def has_param(self,param):
return param in self.get_param_names()
def get_param(self,param):
return getattr(self, param)
def __getattr__(self, param):
"""Provides automatic get/set/add/append methods."""
# Base case: we actually have this param
if param in self.__dict__:
return getattr(self, param)
# Check for the translated variant of the param
realparam = self.__translate(param, sep='')
if realparam in self.__dict__:
return getattr(self, realparam)
if realparam.startswith("get"):
param = self.__translate(param, "get")
return lambda: getattr(self, param)
elif realparam.startswith("set"):
param = self.__translate(param, "set")
return lambda v: self.set_param(param, v,
check_params=True)
elif (realparam.startswith("add") or realparam.startswith("append")):
if realparam.startswith("add"):
param = self.__translate(realparam, "add")
else:
param = self.__translate(realparam, "append")
def array_append(*values):
array = getattr(self, param)
if array is None:
classnm = self.__class__.__name__
errmsg = ("%s object has no array named '%s'"
%( classnm, param ))
raise AttributeError(errmsg)
else:
array.extend(values)
# Cache the function definition
setattr(self, realparam, array_append)
return array_append
errmsg = ("%s object has no parameter '%s'"
% (self.__class__.__name__, param))
raise AttributeError(errmsg)
def set_param(self, param, value, notify=True, check_params=False):
"""Set param with optional notification and validity checks."""
param = param.lower()
if check_params and param not in self.get_param_names():
raise AttributeError("Parameter '%s' not available for %s"
% (param, self.__class__.__name__))
setattr(self, param, value)
if notify:
self.notify_observers(param)
def copy_params(self, model, params=None):
if params is None:
params = self.get_param_names()
for param in params:
self.set_param(param, model.get_param(param))
def __translate(self, param, prefix='', sep='_'):
"""Translates an param name from the external name
used in methods to those used internally. The default
settings strip off '_' so that both get_foo() and getFoo()
are valid incantations."""
return param[len(prefix):].lstrip(sep).lower()
def save(self, filename):
if not has_json():
return
import simplejson
file = open(filename, 'w')
simplejson.dump(self.to_dict(), file, indent=4)
file.close()
def load(self, filename):
if not has_json():
return
import simplejson
file = open(filename, 'r')
ddict = simplejson.load(file)
file.close()
if "__class__" in ddict:
# load params in-place.
del ddict["__class__"]
return self.from_dict(ddict)
@staticmethod
def instance(filename):
if not has_json():
return
import simplejson
file = open(filename, 'r')
ddict = simplejson.load(file)
file.close()
if "__class__" in ddict:
cls = Model.str_to_class(ddict["__class__"])
del ddict["__class__"]
return cls().from_dict(ddict)
else:
return Model().from_dict(ddict)
def from_dict(self, source_dict):
"""Import a complex model from a dictionary.
We store class information in the __class__ variable.
If it looks like a duck, it's a duck."""
if "__class__" in source_dict:
clsstr = source_dict["__class__"]
del source_dict["__class__"]
cls = Model.str_to_class(clsstr)
return cls().from_dict(source_dict)
# Not initiating a clone: load parameters in-place
for param, val in source_dict.iteritems():
self.set_param(param,
self.__obj_from_value(val),
notify=False)
return self
def __obj_from_value(self, val):
# Atoms
if is_atom(val):
return val
# Possibly nested lists
elif is_list(val):
return [ self.__obj_from_value(v) for v in val ]
elif is_dict(val):
# A param that maps to a Model-object
if "__class__" in val:
clsstr = val["__class__"]
cls = Model.str_to_class(clsstr)
del val["__class__"]
return cls().from_dict(val)
newdict = {}
for k, v in val.iteritems():
newdict[k] = self.__obj_from_value(v)
return newdict
# All others
return val
def to_dict(self):
"""
Exports a model to a dictionary.
This simplifies serialization.
"""
params = {"__class__": Model.class_to_str(self)}
for param in self.get_param_names():
params[param] = self.__obj_to_value(getattr(self, param))
return params
def __obj_to_value(self, item):
if is_atom(item):
return item
elif is_list(item):
newlist = [ self.__obj_to_value(i) for i in item ]
return newlist
elif is_dict(item):
newdict = {}
for k,v in item.iteritems():
newdict[k] = self.__obj_to_value(v)
return newdict
elif is_model(item):
return item.to_dict()
else:
raise NotImplementedError("Unknown type:" + str(type(item)))
__INDENT__ = 0
__PREINDENT__ = True
__STRSTACK__ = []
@staticmethod
def INDENT(i=0):
Model.__INDENT__ += i
return ' ' * Model.__INDENT__
def __str__(self):
"""A convenient, recursively-defined stringification method."""
# This avoid infinite recursion on cyclical structures
if self in Model.__STRSTACK__:
return 'REFERENCE' # TODO: implement references? This ain't lisp.
else:
Model.__STRSTACK__.append(self)
io = StringIO()
if Model.__PREINDENT__:
io.write(Model.INDENT())
io.write(self.__class__.__name__ + '(')
Model.INDENT(1)
for param in self.get_param_names():
if param.startswith('_'):
continue
io.write('\n')
inner = Model.INDENT() + param + " = "
value = getattr(self, param)
if type(value) == ListType:
indent = Model.INDENT(1)
io.write(inner + "[\n")
for val in value:
if is_model(val):
io.write(str(val)+'\n')
else:
io.write(indent)
io.write(str(val))
io.write(",\n")
io.write(Model.INDENT(-1))
io.write("],")
else:
Model.__PREINDENT__ = False
io.write(inner)
io.write(str(value))
io.write(',')
Model.__PREINDENT__ = True
io.write('\n' + Model.INDENT(-1) + ')')
value = io.getvalue()
io.close()
Model.__STRSTACK__.remove(self)
return value
@staticmethod
def str_to_class(clstr):
items = clstr.split('.')
modules = items[:-1]
classname = items[-1]
path = None
module = None
for mod in modules:
search = imp.find_module(mod, path)
try:
module = imp.load_module(mod, *search)
if hasattr(module, "__path__"):
path = module.__path__
finally:
if search and search[0]:
search[0].close()
if module:
return getattr(module, classname)
else:
raise Exception("No class found for: %s" % clstr)
@staticmethod
def class_to_str(instance):
modname = instance.__module__
classname = instance.__class__.__name__
return "%s.%s" % (modname, classname)
#############################################################################
def has_json():
try:
import simplejson
return True
except ImportError:
print "Unable to import simplejson." % action
print "You do not have simplejson installed."
print "try: sudo apt-get install simplejson"
return False
#############################################################################
def is_model(item):
return issubclass(item.__class__, Model)
def is_dict(item):
return type(item) is DictType
def is_list(item):
return type(item) is ListType or type(item) is TupleType
def is_atom(item):
return(type(item) in StringTypes
or type(item) is BooleanType
or type(item) is IntType
or type(item) is LongType
or type(item) is FloatType
or type(item) is ComplexType)
def is_function(item):
return type(item) is FunctionType
+1091
View File
File diff suppressed because it is too large Load Diff
+42
View File
@@ -0,0 +1,42 @@
#!/usr/bin/env python
# Copyright (c) 2008 David Aguilar
"""This module provides the Observer design pattern.
"""
class Observer(object):
"""
Observers receive notify(*attributes) messages from their
subjects whenever new data arrives. This notify() message signifies
that an observer should update its internal state/view.
"""
def __init__(self, model):
self.model = model
self.model.add_observer(self)
self.__debug = False
def __del__(self):
self.model.remove_observer(self)
def set_debug(self, enabled):
self.__debug = enabled
def notify(self, *attributes):
"""Called by the model to notify Observers about changes."""
# We can be notified about multiple attribute changes at once
model = self.model
for attr in attributes:
notify = model.get_notify()
model.set_notify(False) # NOTIFY OFF
value = model.get_param(attr)
self.subject_changed(attr, value)
model.set_notify(notify) # NOTIFY ON
def subject_changed(self, attr, value):
"""This method handles updating of the observer/UI.
This must be implemented in each concrete observer class."""
msg = 'Concrete Observers must override subject_changed().'
raise NotImplementedError, msg
+326
View File
@@ -0,0 +1,326 @@
#!/usr/bin/env python
# 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.QtCore import Qt
from PyQt4.QtCore import QObject
from PyQt4.QtCore import SIGNAL
from PyQt4.QtCore import QDate
from PyQt4.QtGui import QComboBox
from PyQt4.QtGui import QDateEdit
from PyQt4.QtGui import QSpinBox
from PyQt4.QtGui import QPixmap
from PyQt4.QtGui import QTextEdit
from PyQt4.QtGui import QLineEdit
from PyQt4.QtGui import QListWidget
from PyQt4.QtGui import QCheckBox
from PyQt4.QtGui import QFontComboBox
from PyQt4.QtGui import QAbstractButton
from PyQt4.QtGui import QSplitter
from PyQt4.QtGui import QAction
from PyQt4.QtGui import QTreeWidget
from PyQt4.QtGui import QTreeWidgetItem
from PyQt4.QtCore import QVariant
from cola.observer import Observer
class QObserver(Observer, QObject):
def __init__(self, model, view, *args, **kwargs):
Observer.__init__(self, model)
QObject.__init__(self)
self.view = view
self.__actions = {}
self.__callbacks = {}
self.__model_to_view = {}
self.__view_to_model = {}
self.__connected = set()
# Call the subclass's startup routine
self.init(model, view, *args, **kwargs)
def init(self, model, view, *args, **kwargs):
pass
def SLOT(self, *args):
"""Default slot to handle all Qt callbacks.
This method delegates to callbacks from add_signals."""
widget = self.sender()
sender = str(widget.objectName())
if sender in self.__view_to_model:
model = self.model
model_param = self.__view_to_model[sender]
if isinstance(widget, QTextEdit):
value = unicode(widget.toPlainText())
model.set_param(model_param, value,
notify=False)
elif isinstance(widget, QLineEdit):
value = unicode(widget.text())
model.set_param(model_param, value)
elif isinstance(widget, QCheckBox):
model.set_param(model_param, widget.isChecked())
elif isinstance(widget, QSpinBox):
model.set_param(model_param, widget.value())
elif isinstance(widget, QFontComboBox):
value = unicode(widget.currentFont().toString())
model.set_param(model_param, value)
elif isinstance(widget, QDateEdit):
fmt = Qt.ISODate
value = str(widget.date().toString(fmt))
model.set_param(model_param, value)
elif isinstance(widget, QListWidget):
row = widget.currentRow()
item = widget.item(row)
if item:
selected = item.isSelected()
else:
selected = False
model.set_param(model_param+'_selected', selected)
model.set_param(model_param+'_index', row)
if selected and row != -1:
model.set_param(model_param+'_item',
model.get_param(model_param)[row])
else:
model.set_param(model_param+'_item', '')
elif isinstance(widget, QTreeWidget):
item = widget.currentItem()
if item:
selected = item.isSelected()
row = widget.indexOfTopLevelItem(item)
else:
selected = False
row = -1
model.set_param(model_param+'_selected', selected)
model.set_param(model_param+'_index', row)
items = model.get_param(model_param)
if selected and row != -1 and row < len(items):
model.set_param(model_param+'_item', items[row])
else:
model.set_param(model_param+'_item', '')
elif isinstance(widget, QComboBox):
idx = widget.currentIndex()
model.set_param(model_param+'_index', idx)
if idx != -1 and model.get_param(model_param):
model.set_param(model_param+'_item',
model.get_param(model_param)[idx])
else:
model.set_param(model_param+'_item', '')
else:
print("SLOT(): Unknown widget:", sender, widget)
if sender in self.__callbacks:
self.__callbacks[sender](*args)
def connect(self, obj, signal_str, *args):
"""Convenience function so that subclasses do not have
to import QtCore.SIGNAL."""
signal = signal_str
if type(signal) is str:
signal = SIGNAL(signal)
return QObject.connect(obj, signal, *args)
def add_signals(self, signal_str, *objects):
"""Connects object's signal to the QObserver."""
for obj in objects:
self.connect(obj, signal_str, self.SLOT)
def add_callbacks(self, **callbacks):
"""Registers callbacks that are called in response to GUI events."""
for sender, callback in callbacks.iteritems():
self.__callbacks[sender] = callback
self.autoconnect(getattr(self.view, sender))
def add_observables(self, *params):
"""This method assumes that widgets and model params
share the same name."""
for param in params:
self.model_to_view(param, getattr(self.view, param))
def model_to_view(self, model_param, *widgets):
"""Binds model params to qt widgets(model->view)"""
self.__model_to_view[model_param] = widgets
for w in widgets:
view = str(w.objectName())
self.__view_to_model[view] = model_param
self.__autoconnect(w)
def autoconnect(self, *widgets):
for w in widgets:
self.__autoconnect(w)
def __autoconnect(self, widget):
"""Automagically connects Qt widgets to QObserver.SLOT"""
if widget in self.__connected:
return
self.__connected.add(widget)
if isinstance(widget, QTextEdit):
self.add_signals('textChanged()', widget)
elif isinstance(widget, QLineEdit):
self.add_signals('textChanged(const QString&)', widget)
elif isinstance(widget, QListWidget):
self.add_signals('itemSelectionChanged()', widget)
self.add_signals('itemClicked(QListWidgetItem *)', widget)
doubleclick = str(widget.objectName())+'_doubleclick'
if hasattr(self, doubleclick):
self.connect(widget, 'itemDoubleClicked(QListWidgetItem *)',
getattr(self, doubleclick))
elif isinstance(widget, 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, 'itemDoubleClicked(QTreeWidgetItem *, int)',
getattr(self, doubleclick))
elif isinstance(widget, QAbstractButton):
self.add_signals('released()', widget)
elif isinstance(widget, QAction):
self.add_signals('triggered()', widget)
elif isinstance(widget, QCheckBox):
self.add_signals('stateChanged(int)', widget)
elif isinstance(widget, QSpinBox):
self.add_signals('valueChanged(int)', widget)
elif isinstance(widget, QFontComboBox):
self.add_signals('currentFontChanged(const QFont&)', widget)
elif isinstance(widget, QSplitter):
self.add_signals('splitterMoved(int,int)', widget)
elif isinstance(widget, QDateEdit):
self.add_signals('dateChanged(const QDate&)', widget)
elif isinstance(widget, QComboBox):
self.add_signals('currentIndexChanged(int)', widget)
else:
raise Exception('Asked to connect unknown widget:\n\t%s => %s'
% (type(widget), str(widget.objectName())))
def add_actions(self, **kwargs):
"""Register view actions that are called in response to
view changes.(view->model)"""
for model_param, callback in kwargs.iteritems():
if type(callback) is list:
self.__actions[model_param] = callback
else:
self.__actions[model_param] = [callback]
def subject_changed(self, param, value):
"""Sends a model param to the view(model->view)"""
if param in self.__model_to_view:
notify = self.model.get_notify()
self.model.set_notify(False)
for widget in self.__model_to_view[param]:
sender = str(widget.objectName())
if isinstance(widget, QSpinBox):
widget.setValue(value)
elif isinstance(widget, QPixmap):
widget.load(value)
elif isinstance(widget, QTextEdit):
widget.setText(value)
elif isinstance(widget, QLineEdit):
widget.setText(value)
elif isinstance(widget, QListWidget):
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.get_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 sender in self.__callbacks:
self.model.set_notify(True)
self.__callbacks[sender]()
self.model.set_notify(False)
else:
self.model.set_param(param+'_item', '')
elif isinstance(widget, QTreeWidget):
self.model.set_param(param+'_item', '')
widget.clear()
for i in value:
item = QTreeWidgetItem([i])
item.setData(0, Qt.UserRole,
QVariant(widget.topLevelItemCount()))
widget.addTopLevelItem(item)
if self.model.has_param(param+'_index'):
idx = self.model.get_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 sender in self.__callbacks:
self.model.set_notify(True)
self.__callbacks[sender]()
self.model.set_notify(False)
else:
self.model.set_param(param+'_item', '')
elif isinstance(widget, QCheckBox):
widget.setChecked(value)
elif isinstance(widget, QFontComboBox):
font = widget.currentFont()
font.fromString(value)
elif isinstance(widget, QDateEdit):
if not value:
return
fmt = Qt.ISODate
date = QDate.fromString(value, fmt)
if date:
widget.setDate(date)
elif isinstance(widget, 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.get_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.set_notify(notify)
if param not in self.__actions:
return
widgets = []
if param in self.__model_to_view:
for widget in self.__model_to_view[param]:
widgets.append(widget)
# Call the model callback w/ the view's widgets as the args
for action in self.__actions[param]:
action(*widgets)
def refresh_view(self, *params):
"""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)
+258
View File
@@ -0,0 +1,258 @@
# Copyright (c) 2008 David Aguilar
"""This module provides miscellaneous Qt utility functions.
"""
import os
from PyQt4 import QtCore
from PyQt4 import QtGui
from PyQt4.QtCore import Qt
from PyQt4.QtGui import QClipboard
from PyQt4.QtGui import QFileDialog
from PyQt4.QtGui import QIcon
from PyQt4.QtGui import QTreeWidget
from PyQt4.QtGui import QListWidgetItem
from PyQt4.QtGui import QTreeWidgetItem
from PyQt4.QtGui import QMessageBox
from cola import utils
from cola.core import encode
LOGGER = None
def log(output, quiet=True, doraise=False):
"""Sends messages to the log window.
"""
if not LOGGER:
return
LOGGER.log(output)
if quiet:
return
LOGGER.show()
if not doraise:
return
raise_logger()
def raise_logger():
"""Raises the log window."""
LOGGER.raise_()
def input(msg, title=None):
"""Presents the user with an input widget and returns the input."""
if title is None:
title = msg
parent = QtGui.qApp.activeWindow()
result = QtGui.QInputDialog.getText(parent, msg, title)
return (unicode(result[0]), result[1])
def close_log_window():
"""Closes the log window."""
LOGGER.hide()
LOGGER.done(0)
def show_output(output, **kwargs):
"""Sends output to the log window."""
if not output:
return
log(output, quiet=False)
def toggle_log_window():
"""Shows/hides the log window."""
if not LOGGER:
return
if LOGGER.isVisible():
LOGGER.hide()
else:
LOGGER.show()
LOGGER.raise_()
def create_listwidget_item(text, filename):
"""Creates a QListWidgetItem with text and the icon at filename."""
item = QListWidgetItem()
item.setIcon(QIcon(filename))
item.setText(text)
return item
def create_treewidget_item(text, filename):
"""Creates a QTreeWidgetItem with text and the icon at filename."""
item = QTreeWidgetItem()
item.setIcon(0, QIcon(filename))
item.setText(0, text)
return item
def information(title, message=None):
"""Launches a QMessageBox information with the
provided title and message."""
if message is None:
message = title
QMessageBox.information(QtGui.qApp.activeWindow(), title, message)
def get_selected_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, Qt.UserRole).toInt()[0]
selected = True
return(id_number, selected)
def get_selected_row(list_widget):
"""Returns a(row_number, is_selected) tuple for a QListWidget."""
row = list_widget.currentRow()
item = list_widget.item(row)
selected = item is not None and item.isSelected()
return(row, selected)
def get_selection_list(listwidget, items):
"""Returns an array of model items that correspond to
the selected QListWidget indices."""
selected = []
itemcount = listwidget.count()
widgetitems = [ listwidget.item(idx) for idx in range(itemcount) ]
for item, widgetitem in zip(items, widgetitems):
if widgetitem.isSelected():
selected.append(item)
return selected
def get_tree_selection(treeitem, items):
"""Returns an array of model items that correspond to
the selected QListWidget indices."""
selected = []
itemcount = treeitem.childCount()
widgetitems = [ treeitem.child(idx) for idx in range(itemcount) ]
for item, widgetitem in zip(items[:len(widgetitems)], widgetitems):
if widgetitem.isSelected():
selected.append(item)
return selected
def get_selected_item(list_widget, items):
"""Returns the selected item in a QListWidget."""
row, selected = get_selected_row(list_widget)
if selected and row < len(items):
return items[row]
else:
return None
def open_dialog(parent, title, filename=None):
"""Creates an Open File dialog and returns a filename."""
return unicode(QFileDialog.getOpenFileName(parent, parent.tr(title),
filename))
def opendir_dialog(parent, title, directory):
"""Creates an Open Directory dialog and returns the file path."""
flags = QtGui.QFileDialog.ShowDirsOnly | QtGui.QFileDialog.DontResolveSymlinks
return unicode(QFileDialog.getExistingDirectory(parent, parent.tr(title),
directory, flags))
def save_dialog(parent, title, filename=''):
"""Creates a Save File dialog and returns a filename."""
return unicode(QFileDialog.getSaveFileName(parent,
parent.tr(title),
filename))
def new_dir_dialog(parent, title, filename=''):
"""Creates a New Directory dialog and returns the file path."""
return unicode(QFileDialog.getSaveFileName(parent,
parent.tr(title),
filename,
os.getcwd(),
parent.tr('New Directory ()')))
def get_icon(filename):
"""Given a basename returns a QIcon from the corresponding cola icon."""
return QIcon(utils.get_icon(filename))
def question(parent, title, message, default=True):
"""Launches a QMessageBox question with the provided title and message.
Passing "default=False" will make "No" the default choice."""
yes = QMessageBox.Yes
no = QMessageBox.No
buttons = yes | no
if default:
default = yes
else:
default = no
result = QMessageBox.question(parent, title, message, buttons, default)
return result == QMessageBox.Yes
def set_clipboard(text):
"""Sets the copy/paste buffer to text."""
QtGui.qApp.clipboard().setText(text, QClipboard.Clipboard)
QtGui.qApp.clipboard().setText(text, QClipboard.Selection)
def set_selected_item(widget, idx):
"""Sets a the currently selected item to the item at index idx."""
if type(widget) is 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."""
return unicode(QtGui.qApp.translate('', txt))
def get_icon_file(filename, staged=False, untracked=False):
"""Returns a file path representing a corresponding file path."""
if staged:
if os.path.exists(encode(filename)):
icon_file = utils.get_icon('staged.png')
else:
icon_file = utils.get_icon('removed.png')
elif untracked:
icon_file = utils.get_icon('untracked.png')
else:
icon_file = utils.get_file_icon(filename)
return icon_file
def get_icon_for_file(filename, staged=False, untracked=False):
"""Returns a QIcon for a particular file path."""
icon_file = get_icon_file(filename, staged=staged, untracked=untracked)
return get_icon(icon_file)
def create_listitem(filename, staged=False, untracked=False):
"""Given a filename, return a QListWidgetItem suitable
for adding to a QListWidget. "staged" and "untracked"
controls whether to use the appropriate icons."""
icon_file = get_icon_file(filename, staged, untracked)
return create_listwidget_item(filename, icon_file)
def create_treeitem(filename, staged=False, untracked=False):
"""Given a filename, return a QListWidgetItem suitable
for adding to a QListWidget. "staged" and "untracked"
controls whether to use the appropriate icons."""
icon_file = get_icon_file(filename, staged=staged, untracked=untracked)
return create_treewidget_item(filename, icon_file)
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(get_icon_for_file(model_item, staged, untracked))
def update_listwidget(widget, items, staged=True,
untracked=False, append=False):
"""Populate a QListWidget with custom icon items."""
if not append:
widget.clear()
add_items(widget, [ create_listitem(i, staged, untracked) for i in items ])
def set_listwidget_strings(widget, items):
"""Sets a list widget to the strings passed in items."""
widget.clear()
add_items(widget, [ QListWidgetItem(i) for i in items ])
+32
View File
@@ -0,0 +1,32 @@
#!/usr/bin/env python
# Copyright (c) 2008 David Aguilar
"""This handles saving complex settings such as bookmarks, etc.
"""
HAS_SIMPLEJSON = False
try:
import simplejson
HAS_SIMPLEJSON = True
except ImportError:
pass
import os
import user
from cola.model import Model
class SettingsModel(Model):
def init(self):
self.create( bookmarks = [] )
if not HAS_SIMPLEJSON:
return
settings = self.path()
if os.path.exists(settings):
self.load(settings)
def path(self):
return os.path.join(user.home, '.cola')
def save_all_settings(self):
if not HAS_SIMPLEJSON:
return
self.save(self.path())
+239
View File
@@ -0,0 +1,239 @@
#!/usr/bin/python
# Copyright (c) 2008 David Aguilar
"""This module provides SyntaxHighlighter classes.
These classes are installed onto specific cola widgets and
implement the diff syntax highlighting.
"""
import re
from PyQt4.QtCore import Qt
from PyQt4.QtCore import pyqtProperty
from PyQt4.QtCore import QVariant
from PyQt4.QtGui import QFont
from PyQt4.QtGui import QSyntaxHighlighter
from PyQt4.QtGui import QTextCharFormat
from PyQt4.QtGui import QColor
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 = {}
default_colors = {}
def _install_default_colors():
def color(c, a=255):
qc = QColor(c)
qc.setAlpha(a)
return qc
default_colors.update({
'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),
})
_install_default_colors()
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.init(doc, *args, **kwargs)
self.reset()
def init(self, *args, **kwargs):
pass
def reset(self):
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('')):]
if rule in _RGX_CACHE:
regex = _RGX_CACHE[rule]
else:
regex = re.compile(rule)
_RGX_CACHE[rule] = regex
self._rules.append((regex, formats, terminal,))
def get_formats(self, line):
matched = []
for regex, fmts, terminal in self._rules:
match = regex.match(line)
if match:
matched.append([match, fmts])
if terminal:
return matched
return matched
def mkformat(self, fg=None, bg=None, bold=False):
format = QTextCharFormat()
if fg: format.setForeground(fg)
if bg: format.setBackground(bg)
if bold: format.setFontWeight(QFont.Bold)
return format
def highlightBlock(self, qstr):
ascii = qstr.toAscii().data()
if not ascii: return
formats = self.get_formats(ascii)
if not formats: return
for match, fmts in formats:
start = match.start()
end = match.end()
groups = match.groups()
# No groups in the regex, assume this is a single rule
# that spans the entire line
if not groups:
self.setFormat(0, len(ascii), fmts)
continue
# Groups exist, rule is a tuple corresponding to group
for grpidx, group in enumerate(groups):
# allow empty matches
if not group: continue
# allow None as a no-op format
length = len(group)
if fmts[grpidx]:
self.setFormat(start, start+length,
fmts[grpidx])
start += length
def set_colors(self, colordict):
for attr, val in colordict.items():
setattr(self, attr, val)
class DiffSyntaxHighlighter(GenericSyntaxHighligher):
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))
class LogSyntaxHighlighter(GenericSyntaxHighligher):
def generate_rules(self):
info = self.mkformat(self.color_info, bold=True)
emphasis = self.mkformat(self.color_emphasis, bold=True)
date = self.mkformat(self.color_date, bold=True)
info_rgx = '^([^:]+:)(.*)$'
date_rgx = TERMINAL('^\w{3}\W+\w{3}\W+\d+\W+[:0-9]+\W+\d{4}$')
self.create_rules(date_rgx, date,
info_rgx, (info, emphasis))
# This is used as a mixin to generate property callbacks
def accessors(attr):
private_attr = '_'+attr
def getter(self):
if private_attr in self.__dict__:
return self.__dict__[private_attr]
else:
return None
def setter(self, value):
self.__dict__[private_attr] = value
self.reset_syntax()
return (getter, setter)
def install_theme_properties(cls):
# Diff GUI colors -- this is controllable via the style sheet
for name in default_colors:
setattr(cls, name, pyqtProperty('QColor', *accessors(name)))
def set_theme_properties(widget):
for name, color in default_colors.items():
widget.setProperty(name, QVariant(color))
if __name__ == '__main__':
import sys
from PyQt4 import QtCore, QtGui
class SyntaxTestDialog(QtGui.QDialog):
def __init__(self, parent):
QtGui.QDialog.__init__(self, parent)
self.setupUi(self)
def setupUi(self, dialog):
dialog.resize(QtCore.QSize(QtCore.QRect(0,0,720,512).size()).expandedTo(dialog.minimumSizeHint()))
self.vboxlayout = QtGui.QVBoxLayout(dialog)
self.vboxlayout.setObjectName('vboxlayout')
self.output_text = QtGui.QTextEdit(dialog)
font = QtGui.QFont()
font.setFamily('Monospace')
font.setPointSize(13)
self.output_text.setFont(font)
self.output_text.setAcceptDrops(False)
self.vboxlayout.addWidget(self.output_text)
self.syntax = DiffSyntaxHighlighter(self.output_text.document())
app = QtGui.QApplication(sys.argv)
dialog = SyntaxTestDialog(app.activeWindow())
dialog.show()
dialog.exec_()
+428
View File
@@ -0,0 +1,428 @@
#!/usr/bin/env python
# Copyright (c) 2008 David Aguilar
"""This module provides miscellaneous utility functions."""
import os
import re
import sys
import platform
import subprocess
from glob import glob
from cStringIO import StringIO
from cola import git
from cola.git import shell_quote
from cola.core import encode, decode
from cola.exception import ColaException
PREFIX = os.path.realpath(os.path.dirname(os.path.dirname(sys.argv[0])))
QMDIR = os.path.join(PREFIX, 'share', 'cola', 'qm')
ICONSDIR = os.path.join(PREFIX, 'share', 'cola', 'icons')
STYLEDIR = os.path.join(PREFIX, 'share', 'cola', 'styles')
DOCDIR = os.path.join(PREFIX, 'share', 'doc', 'cola')
KNOWN_FILE_TYPES = {
'ascii c': 'c.png',
'python': 'script.png',
'ruby': 'script.png',
'shell': 'script.png',
'perl': 'script.png',
'java': 'script.png',
'assembler': 'binary.png',
'binary': 'binary.png',
'byte': 'binary.png',
'image': 'image.png',
}
def run_cmd(*command):
"""
Runs a *command argument list and returns the output.
e.g. run_cmd("echo", "hello", "world")
"""
return git.Git.execute(command)
def get_qm_for_locale(locale):
"""Returns the .qm file for a particular $LANG values."""
regex = re.compile(r'([^\.])+\..*$')
match = regex.match(locale)
if match:
locale = match.group(1)
basename = locale.split('_')[0]
return os.path.join(QMDIR, basename +'.qm')
def get_resource_dirs(styledir):
"""Returns all directories underneath the share/cola/styles directory."""
return [ r for r in glob(styledir+ '/*') if os.path.isdir(r) ]
def get_stylesheet(name):
"""Returns the path to a stylesheet within the cola install tree."""
stylesheet = os.path.join(STYLEDIR, name+'.qss')
if os.path.exists(stylesheet):
return stylesheet
else:
return None
def get_htmldocs():
"""Returns the path to the cola html documentation."""
return os.path.join(DOCDIR, 'git-cola.html')
def ident_file_type(filename):
"""Returns an icon based on the contents of filename."""
if os.path.exists(filename):
fileinfo = run_cmd('file','-b',filename)
for filetype, iconname in KNOWN_FILE_TYPES.iteritems():
if filetype in fileinfo.lower():
return iconname
else:
return 'removed.png'
# Fallback for modified files of an unknown type
return 'generic.png'
def get_file_icon(filename):
"""
Returns the full path to an icon file corresponding to
filename"s contents.
"""
icon_file = ident_file_type(filename)
return get_icon(icon_file)
def get_icon(icon_file):
"""Returns the full path to an icon file given a basename."""
return os.path.join(ICONSDIR, icon_file)
def fork(*args):
"""Launches a command in the background."""
args = tuple([ encode(a) for a in args ])
if os.name in ('nt', 'dos'):
for path in os.environ['PATH'].split(os.pathsep):
filename = os.path.join(path, args[0]) + ".exe"
if os.path.exists(filename):
try:
return os.spawnv(os.P_NOWAIT, filename, args)
except os.error:
pass
raise IOError('cannot find executable: %s' % program)
else:
argv = map(shell_quote, args)
return os.system(' '.join(argv) + '&')
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):
"""Avoid os.path.basename because we are explicitly
parsing git"s output, which contains /"s regardless
of platform (a.t.m.)
"""
base_regex = re.compile('(.*?/)?([^/]+)$')
match = base_regex.match(path)
if match:
return match.group(2)
else:
return pathstr
def slurp(path):
"""Slurps a filepath into a string."""
file = open(path)
slushy = file.read()
file.close()
return decode(slushy)
def write(path, contents):
"""Writes a string to a file."""
file = open(path, 'w')
file.write(encode(contents))
file.close()
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) = model.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 = \
model.diff_helper(filename=filename,
branch=branch,
with_diff_header=True,
cached=cached and not bool(branch),
reverse=bool(branch))
def write_diff(self,filename,which,selected=False,noop=False):
"""Writes a new diff corresponding to the user's selection."""
if not noop and which < len(self.diffs):
diff = self.diffs[which]
write(filename, self.header + '\n' + diff + '\n')
return True
else:
return False
def get_diffs(self):
"""Returns the list of diffs."""
return self.__diffs
def get_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 get_spans(self):
"""Returns the line spans of each hunk."""
return self.__diff_spans
def get_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.get_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.get_diffs_for_range(start,end)
def get_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 get_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.splitlines()):
match = self.__header_re.match(line)
if match:
self.__headers.append([
int(match.group(1)),
int(match.group(2)),
int(match.group(3)),
int(match.group(4))
])
self.__diffs.append( [line] )
line_len = len(line) + 1 #\n
self.__diff_spans.append([total_offset,
total_offset + line_len])
total_offset += line_len
self.__diff_offsets.append(total_offset)
self.__idx += 1
else:
if self.__idx < 0:
errmsg = 'Malformed diff?\n\n%s' % diff
raise AssertionError, errmsg
line_len = len(line) + 1
total_offset += line_len
self.__diffs[self.__idx].append(line)
self.__diff_spans[-1][-1] += line_len
self.__diff_offsets[self.__idx] += line_len
def process_diff_selection(self, selected, offset, selection,
apply_to_worktree=False):
"""Processes a diff selection and applies changes to the work tree
or index."""
if selection:
start = self.fwd_diff.index(selection)
end = start + len(selection)
self.set_diffs_to_range(start, end)
else:
self.set_diff_to_offset(offset)
selected = False
# Process diff selection only
if selected:
for idx in self.selected:
contents = self.get_diff_subset(idx, start, end)
if contents:
tmpfile = self.model.get_tmp_filename()
write(tmpfile, contents)
if apply_to_worktree:
self.model.apply_diff_to_worktree(tmpfile)
else:
self.model.apply_diff(tmpfile)
os.unlink(tmpfile)
# Process a complete hunk
else:
for idx, diff in enumerate(self.diffs):
tmpfile = self.model.get_tmp_filename()
if self.write_diff(tmpfile,idx):
if apply_to_worktree:
self.model.apply_diff_to_worktree(tmpfile)
else:
self.model.apply_diff(tmpfile)
os.unlink(tmpfile)
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_input(input):
"""Removes shell metacharacters from a string."""
for c in """ \t!@#$%^&*()\\;,<>"'[]{}~|""":
input = input.replace(c, '_')
return input
def is_linux():
"""Is this a linux machine?"""
return platform.system() == 'Linux'
def is_debian():
"""Is it debian?"""
return os.path.exists('/usr/bin/apt-get')
def is_broken():
"""Is it windows or mac? (e.g. is running git-mergetool non-trivial?)"""
return (platform.system() == 'Windows'
or 'Macintosh' in platform.platform())
+77
View File
@@ -0,0 +1,77 @@
# Copyright (c) 2008 David Aguilar
"""This module inspects the cola repository and calculates
cola version numbers.
"""
import re
import os
import sys
from cola.exception import ColaException
from cola import git
from cola import utils
class VersionUnavailable(ColaException):
pass
def git_describe_version():
"""Inspect the cola git repository and return the current version."""
path = sys.path[0]
try:
v = git.Git.execute(['git', 'describe', '--tags', '--abbrev=4'])
except git.GitCommandError, e:
raise VersionUnavailable(str(e))
if not re.match(r'^v[0-9]', v):
raise VersionUnavailable('%s: bad version' % v)
try:
dirty = git.Git.execute(['git', 'diff-index', '--name-only', 'HEAD'])
except git.GitCommandError, e:
raise VersionUnavailable(str(e))
if dirty:
v += '-dirty'
return re.sub('-', '.', utils.strip_prefix('v', v))
def builtin_version():
"""Return the builtin version or calculate it as needed."""
try:
import builtin_version as bv
except ImportError:
raise VersionUnavailable()
else:
return bv.version
def _builtin_version_file(ext = 'py'):
"""Returns the path to cola/builtin_version.py."""
return os.path.join(sys.path[0], 'cola', 'builtin_version.%s' % ext)
def write_builtin_version():
"""Writes cola/builtin_version.py."""
try:
v = git_describe_version()
except VersionUnavailable:
return
f = file(_builtin_version_file(), 'w')
f.write('# This file was generated automatically. Do not edit by hand.\n'
'version = %r\n' % v)
def delete_builtin_version():
"""Deletes cola/builtin_version.py."""
for ext in ['py', 'pyc', 'pyo']:
fn = _builtin_version_file(ext)
if os.path.exists(fn):
os.remove(fn)
def get_version():
"""Returns the builtin version or calculates the current version."""
for v in [builtin_version, git_describe_version]:
try:
return v()
except VersionUnavailable:
pass
return 'unknown-version'
version = get_version()
git_min_ver = '1.5.2' #: minimum git version
python_min_ver = '2.4' #: minimum python version
pyqt_min_ver = '4.3' #: minimum PyQt version
+142
View File
@@ -0,0 +1,142 @@
"""This module creates simple wrapper classes around the auto-generated
.ui classes.
"""
import sys
import time
from PyQt4.QtGui import QDialog
from PyQt4.QtGui import QListWidget
from PyQt4.QtGui import qApp
from PyQt4.QtCore import SIGNAL
from cola.syntax import DiffSyntaxHighlighter
from cola.syntax import LogSyntaxHighlighter
from cola.core import decode
try:
from main import View
from main import CreateStandardView
from cola.gui.bookmark import Ui_bookmark
from cola.gui.branchview import Ui_branchview
from cola.gui.combo import Ui_combo
from cola.gui.commit import Ui_commit
from cola.gui.compare import Ui_compare
from cola.gui.createbranch import Ui_createbranch
from cola.gui.items import Ui_items
from cola.gui.logger import Ui_logger
from cola.gui.merge import Ui_merge
from cola.gui.options import Ui_options
from cola.gui.remote import Ui_remote
from cola.gui.search import Ui_search
from cola.gui.stash import Ui_stash
except ImportError:
sys.stderr.write('\nThe cola gui modules have not been built.\n'
'Try running "make" in the cola source tree.\n')
sys.exit(-1)
# These are views that do not contain any custom methods
OptionsView = CreateStandardView(Ui_options, QDialog)
BranchCompareView = CreateStandardView(Ui_branchview, QDialog)
CreateBranchView = CreateStandardView(Ui_createbranch, QDialog)
BookmarkView = CreateStandardView(Ui_bookmark, QDialog)
StashView = CreateStandardView(Ui_stash, QDialog)
CompareView = CreateStandardView(Ui_compare, QDialog)
class LogView(CreateStandardView(Ui_logger, QDialog)):
"""A simple dialog to display command logs."""
def init(self, parent=None, output=None):
self.setWindowTitle(self.tr('Git Command Log'))
self.syntax = LogSyntaxHighlighter(self.output_text.document())
if output:
self.set_output(output)
def clear(self):
self.output_text.clear()
def set_output(self, output):
self.output_text.setText(output)
def log(self, output):
if not output:
return
cursor = self.output_text.textCursor()
cursor.movePosition(cursor.End)
text = self.output_text
cursor.insertText(time.asctime() + '\n')
for line in unicode(decode(output)).splitlines():
cursor.insertText(line + '\n')
cursor.insertText('\n')
cursor.movePosition(cursor.End)
text.setTextCursor(cursor)
class ItemView(object):
def init(self, parent, title="", items=[], dblclick=None):
self.setWindowTitle(title)
self.items_widget.addItems(items)
if dblclick and type(self.items_widget) is QListWidget:
self.connect(self.items_widget,
SIGNAL('itemDoubleClicked(QListWidgetItem*)'),
dblclick)
def idx(self):
return 0
def get_selected(self):
geom = qApp.desktop().screenGeometry()
width = geom.width()
height = geom.height()
x = self.parent_view.x() + self.parent_view.width()/2 - self.width()/2
y = self.parent_view.y() + self.parent_view.height()/3 - self.height()/2
self.move(x, y)
self.show()
if self.exec_() == QDialog.Accepted:
return self.value()
else:
return None
class ComboView(CreateStandardView(Ui_combo, QDialog, ItemView), ItemView):
"""A dialog for choosing branches."""
def idx(self):
return self.items_widget.currentIndex()
def value(self):
return str(self.items_widget.currentText())
class ListView(CreateStandardView(Ui_items, QDialog, ItemView), ItemView):
"""A dialog for an item from a list."""
def idx(self):
return self.items_widget.currentRow()
def value(self):
item = self.items_widget.currentItem()
if not item:
return None
return str(item.text())
class CommitView(CreateStandardView(Ui_commit, QDialog)):
def init(self, parent=None, title=None):
if title: self.setWindowTitle(title)
# Make the list widget slighty larger
self.splitter.setSizes([ 50, 200 ])
self.syntax = DiffSyntaxHighlighter(self.commit_text.document(),
whitespace=False)
class SearchView(CreateStandardView(Ui_search, QDialog)):
def init(self, parent=None):
self.input.setFocus()
self.syntax = DiffSyntaxHighlighter(self.commit_text.document(),
whitespace=False)
class MergeView(CreateStandardView(Ui_merge, QDialog)):
def init(self, parent=None):
self.revision.setFocus()
class RemoteView(CreateStandardView(Ui_remote, QDialog)):
def init(self, parent=None, button_text=''):
if button_text:
self.action_button.setText(button_text)
self.setWindowTitle(button_text)
def select_first_remote(self):
item = self.remotes.item(0)
if item:
self.remotes.setItemSelected(item, True)
self.remotes.setCurrentItem(item)
self.remotename.setText(item.text())
return True
else:
return False
+290
View File
@@ -0,0 +1,290 @@
"""This view provides the main git-cola user interface.
"""
from PyQt4.QtCore import Qt
from PyQt4.QtCore import SIGNAL
from PyQt4.QtGui import QMainWindow
from cola import qtutils
from cola import syntax
from cola.syntax import DiffSyntaxHighlighter
from cola.gui.main import Ui_main
def CreateStandardView(uiclass, qtclass, *classes):
"""CreateStandardView returns a class closure of uiclass and qtclass.
This class performs the standard setup common to all view classes."""
class StandardView(uiclass, qtclass):
def __init__(self, parent=None, *args, **kwargs):
qtclass.__init__(self, parent)
uiclass.__init__(self)
self.parent_view = parent
syntax.set_theme_properties(self)
self.setupUi(self)
self.init(parent, *args, **kwargs)
for cls in classes:
cls.init(self, parent, *args, **kwargs)
def init(self, parent, *args, **kwargs):
pass
def get_properties(self):
# user-definable color properties
props = {}
for name in syntax.default_colors:
props[name] = getattr(self, '_'+name)
return props
def reset_syntax(self):
if hasattr(self, 'syntax') and self.syntax:
self.syntax.set_colors(self.get_properties())
self.syntax.reset()
syntax.install_theme_properties(StandardView)
return StandardView
class View(CreateStandardView(Ui_main, QMainWindow)):
"""The main cola interface."""
IDX_STAGED = 0
IDX_MODIFIED = 1
IDX_UNMERGED = 2
IDX_UNTRACKED = 3
IDX_END = 4
def init(self, parent=None):
self.set_display = self.display_text.setText
self.amend_is_checked = self.amend_radio.isChecked
self.action_undo = self.commitmsg.undo
self.action_redo = self.commitmsg.redo
self.action_paste = self.commitmsg.paste
self.action_select_all = self.commitmsg.selectAll
# Qt does not support noun/verbs
self.commit_button.setText(qtutils.tr('Commit@@verb'))
self.commit_menu.setTitle(qtutils.tr('Commit@@verb'))
# Default to creating a new commit(i.e. not an amend commit)
self.new_commit_radio.setChecked(True)
self.toolbar_show_log =\
self.toolbar.addAction(qtutils.get_icon('git.png'),
'Show/Hide Log Window')
self.toolbar_show_log.setEnabled(True)
# Diff/patch syntax highlighter
self.syntax = DiffSyntaxHighlighter(self.display_text.document())
# Display the current column
self.connect(self.commitmsg,
SIGNAL('cursorPositionChanged()'),
self.show_current_column)
# Install default icons
self.setup_icons()
# Initialize the seen tree widget indexes
self._seen_indexes = set()
# Initialize the GUI to show 'Column: 00'
self.show_current_column()
def set_staged(self, items):
"""Adds items to the 'Staged' subtree."""
self._set_subtree(items, View.IDX_STAGED, staged=True)
def set_modified(self, items):
"""Adds items to the 'Modified' subtree."""
self._set_subtree(items, View.IDX_MODIFIED)
def set_unmerged(self, items):
"""Adds items to the 'Unmerged' subtree."""
self._set_subtree(items, View.IDX_UNMERGED)
def set_untracked(self, items):
"""Adds items to the 'Untracked' subtree."""
self._set_subtree(items, View.IDX_UNTRACKED)
def _set_subtree(self, items, idx, staged=False, untracked=False):
parent = self.status_tree.topLevelItem(idx)
parent.takeChildren()
for item in items:
treeitem = qtutils.create_treeitem(item,
staged=staged,
untracked=untracked)
parent.addChild(treeitem)
if idx not in self._seen_indexes and items:
self._seen_indexes.add(idx)
self.status_tree.expandItem(parent)
if items:
self.status_tree.setItemHidden(parent, False)
else:
self.status_tree.setItemHidden(parent, True)
def expand_status(self):
for idx in xrange(0, View.IDX_END):
item = self.status_tree.topLevelItem(idx)
if item:
self.status_tree.expandItem(item)
def get_index_for_item(self, item):
"""Given an item, returns the index of the item.
The indexes for unstaged items are grouped such that
the index of unmerged[1] = len(modified) + 1, etc.
"""
if not item:
return False, -1
parent = item.parent()
if not parent:
return False, -1
tree = self.status_tree
pidx = tree.indexOfTopLevelItem(parent)
if pidx == View.IDX_STAGED:
return True, parent.indexOfChild(item)
elif pidx == View.IDX_MODIFIED:
return False, parent.indexOfChild(item)
count = tree.topLevelItem(View.IDX_MODIFIED).childCount()
if pidx == View.IDX_UNMERGED:
return False, count + parent.indexOfChild(item)
count += tree.topLevelItem(View.IDX_UNMERGED).childCount()
if pidx == View.IDX_UNTRACKED:
return False, count + parent.indexOfChild(item)
return False, -1
def get_selection(self):
tree = self.status_tree
item = tree.currentItem()
parent = item.parent()
if not parent:
return -1, False
idx = parent.indexOfChild(item)
pidx = tree.indexOfTopLevelItem(parent)
if pidx == View.IDX_STAGED or pidx == View.IDX_MODIFIED:
return idx, tree.isItemSelected(item)
elif pidx == View.IDX_UNMERGED:
num_modified = tree.topLevelItem(View.IDX_MODIFIED).childCount()
return idx + num_modified, tree.isItemSelected(item)
elif pidx == View.IDX_UNTRACKED:
num_modified = tree.topLevelItem(View.IDX_MODIFIED).childCount()
num_unmerged = tree.topLevelItem(View.IDX_UNMERGED).childCount()
return idx + num_modified + num_unmerged, tree.isItemSelected(item)
return -1, False
def get_staged_item(self, itemidx):
return self._get_subtree_item(View.IDX_STAGED, itemidx)
def get_modified_item(self, itemidx):
return self._get_subtree_item(View.IDX_MODIFIED, itemidx)
def get_unstaged_item(self, itemidx):
tree = self.status_tree
# is it modified?
item = tree.topLevelItem(View.IDX_MODIFIED)
count = item.childCount()
if itemidx < count:
return item.child(itemidx)
# is it unmerged?
item = tree.topLevelItem(View.IDX_UNMERGED)
count += item.childCount()
if itemidx < count:
return item.child(itemidx)
# is it untracked?
item = tree.topLevelItem(View.IDX_UNTRACKED)
count += item.childCount()
if itemidx < count:
return item.child(itemidx)
# Nope..
return None
def _get_subtree_item(self, idx, itemidx):
parent = self.status_tree.topLevelItem(idx)
return parent.child(itemidx)
def get_unstaged(self, items):
tree = self.status_tree
num_modified = tree.topLevelItem(View.IDX_MODIFIED).childCount()
num_unmerged = tree.topLevelItem(View.IDX_UNMERGED).childCount()
modified = self.get_modified(items)
unmerged = self.get_unmerged(items[num_modified:])
untracked = self.get_untracked(items[num_modified+num_unmerged:])
return modified + unmerged + untracked
def get_staged(self, items):
return self._get_subtree_selection(View.IDX_STAGED, items)
def get_modified(self, items):
return self._get_subtree_selection(View.IDX_MODIFIED, items)
def get_unmerged(self, items):
return self._get_subtree_selection(View.IDX_UNMERGED, items)
def get_untracked(self, items):
return self._get_subtree_selection(View.IDX_UNTRACKED, items)
def _get_subtree_selection(self, idx, items):
item = self.status_tree.topLevelItem(idx)
return qtutils.get_tree_selection(item, items)
def setup_icons(self):
staged = self.status_tree.topLevelItem(View.IDX_STAGED)
staged.setIcon(0, qtutils.get_icon('plus.png'))
modified = self.status_tree.topLevelItem(View.IDX_MODIFIED)
modified.setIcon(0, qtutils.get_icon('modified.png'))
unmerged = self.status_tree.topLevelItem(View.IDX_UNMERGED)
unmerged.setIcon(0, qtutils.get_icon('unmerged.png'))
untracked = self.status_tree.topLevelItem(View.IDX_UNTRACKED)
untracked.setIcon(0, qtutils.get_icon('untracked.png'))
def set_info(self, txt):
try:
translated = self.tr(unicode(txt))
except:
translated = unicode(txt)
self.statusBar().showMessage(translated)
def show_editor(self):
self.editor_dock.raise_()
def show_diff(self):
self.tabwidget.setCurrentIndex(0)
def action_cut(self):
self.action_copy()
self.action_delete()
def action_copy(self):
cursor = self.commitmsg.textCursor()
selection = cursor.selection().toPlainText()
qtutils.set_clipboard(selection)
def action_delete(self):
self.commitmsg.textCursor().removeSelectedText()
def reset_checkboxes(self):
self.new_commit_radio.setChecked(True)
self.amend_radio.setChecked(False)
def reset_display(self):
self.set_display('')
self.set_info('')
def copy_display(self):
cursor = self.display_text.textCursor()
selection = cursor.selection().toPlainText()
qtutils.set_clipboard(selection)
def diff_selection(self):
cursor = self.display_text.textCursor()
offset = cursor.position()
selection = unicode(cursor.selection().toPlainText())
return offset, selection
def selected_line(self):
cursor = self.display_text.textCursor()
offset = cursor.position()
contents = unicode(self.display_text.toPlainText())
while (offset >= 1
and contents[offset-1]
and contents[offset-1] != '\n'):
offset -= 1
data = contents[offset:]
if '\n' in data:
line, rest = data.split('\n', 1)
else:
line = data
return line
def display(self, text):
self.set_display(text)
self.diff_dock.raise_()
def show_current_column(self):
cursor = self.commitmsg.textCursor()
colnum = cursor.columnNumber()
self.column_label.setText('Column: %02d' % colnum)
-252
View File
@@ -1,252 +0,0 @@
import os
import re
import time
import commands
import ugitutils
def git_add (to_add):
'''Invokes 'git add' to index the filenames in to_add.'''
if not to_add: return 'ERROR: No files to add.'
argv = [ 'git', 'add' ]
for filename in to_add:
argv.append (ugitutils.shell_quote (filename))
cmd = ' '.join (argv)
return 'Running:\t%s\n%s\n%s added successfully' % (
cmd, commands.getoutput (cmd), ', '.join (to_add) )
def git_add_or_remove (to_process):
'''Invokes 'git add' to index the filenames in to_process that exist
and 'git rm' for those that do not exist.'''
if not to_process: return 'ERROR: No files to add or remove.'
to_add = []
output = ''
for filename in to_process:
if os.path.exists (filename):
to_add.append (filename)
if to_add:
output += git_add (to_add) + '\n\n'
if len(to_add) == len(to_process):
# to_process only contained unremoved files --
# short-circuit the removal checks
return output
# Process files to add
argv = [ 'git', 'rm' ]
for filename in to_process:
if not os.path.exists (filename):
argv.append (ugitutils.shell_quote (filename))
cmd = ' '.join (argv)
return output + 'Running: %s\n%s' % ( cmd, commands.getoutput (cmd) )
def git_branch():
'''Returns 'git branch''s output in a list.'''
return commands.getoutput ('git branch').split ('\n')
def git_cherry_pick (revs, commit=False):
'''Cherry-picks each revision into the current branch.'''
if not revs:
return 'ERROR: No revisions selected for cherry-picking.'''
cmd = 'git cherry-pick '
if not commit: cmd += '-n '
output = []
for rev in revs:
output.append ('Cherry-picking: ' + rev)
output.append (commands.getoutput (cmd + rev))
output.append ('')
return '\n'.join (output)
def git_commit (msg, amend, commit_all, files):
'''Creates a git commit. 'commit_all' triggers the -a
flag to 'git commit.' 'amend' triggers --amend.
'files' is a list of files to use for commits without -a.'''
if not msg:
return 'ERROR: No commit message was provided.'
# Allow TMPDIR/TMP with a fallback to /tmp
tmpdir = os.getenv ('TMPDIR', os.getenv ('TMP', '/tmp'))
# Sure, this is a potential "security risk," but if someone
# is trying to intercept/re-write commit messages on your system,
# then you probably have bigger problems to worry about.
tmpfile = os.path.join (tmpdir,
'ugit.%s.%s' % ( os.getuid(), time.time() ))
argv = [ 'git', 'commit', '-F', tmpfile ]
if amend: argv.append ('--amend')
if commit_all:
argv.append ('-a')
else:
if not files:
return 'ERROR: No files selected for commit.'
argv.append ('--')
for file in files:
argv.append (ugitutils.shell_quote (file))
# Create the commit message file
file = open (tmpfile, 'w')
file.write (msg)
file.close()
# Run 'git commit'
cmd = ' '.join (argv)
output = commands.getoutput (cmd)
os.unlink (tmpfile)
return 'Running:\t%s\n%s' % ( cmd, output )
def git_current_branch():
'''Parses 'git branch' to find the current branch.'''
for branch in git_branch():
if branch.startswith ('* '):
return branch.lstrip ('* ')
raise Exception, 'No current branch. Detached HEAD?'
def git_diff (filename, staged=True):
'''Invokes git_diff on filename. Passing staged=True adds
diffs the index against HEAD (i.e. --cached).'''
deleted = False
argv = [ 'git', 'diff', '--color']
if staged:
deleted = not os.path.exists (filename)
argv.append ('--cached')
argv.append ('--')
argv.append (ugitutils.shell_quote (filename))
diff = commands.getoutput (' '.join (argv))
diff_lines = diff.split ('\n')
output = []
start = False
del_tag = 'deleted file mode '
for line in diff_lines:
if not start and '@@ ' in line and ' @@' in line:
start = True
if start or (deleted and del_tag in line):
output.append (line)
return '\n'.join (output)
def git_diff_stat ():
'''Returns the latest diffstat.'''
return commands.getoutput ('git diff --color --stat HEAD^')
def git_format_patch (revs, use_range):
'''Exports patches revs in the 'ugit-patches' subdirectory.
If use_range is True, a commit range is passed to git format-patch.'''
cmd = 'git format-patch --thread --patch-with-stat -o ugit-patches '
header = 'Generated Patches:'
if len (revs) > 1:
cmd += '-n '
if use_range:
rev_range = '%s^..%s' % ( revs[-1], revs[0] )
return header + '\n' + commands.getoutput (cmd + rev_range)
output = [ header ]
num_patches = 1
for idx, rev in enumerate (revs):
real_idx = idx + num_patches
revcmd = cmd + '-1 --start-number %d %s' % (real_idx, rev)
output.append (commands.getoutput (revcmd))
num_patches += output[-1].count ('\n')
return '\n'.join (output)
def git_log (oneline=True, all=False):
'''Returns a pair of parallel arrays listing the revision sha1's
and commit summaries.'''
argv = [ 'git', 'log' ]
if oneline: argv.append ('--pretty=oneline')
if all: argv.append ('--all')
revs = []
summaries = []
regex = re.compile ('(\w+)\W(.*)')
output = commands.getoutput (' '.join (argv))
for line in output.split ('\n'):
match = regex.match (line)
if match:
revs.append (match.group (1))
summaries.append (match.group (2))
return ( revs, summaries )
def git_reset (to_unstage):
'''Use 'git reset' to unstage files from the index.'''
if not to_unstage: return 'ERROR: No files to reset.'
argv = [ 'git', 'reset', '--' ]
for filename in to_unstage:
argv.append (ugitutils.shell_quote (filename))
cmd = ' '.join (argv)
return 'Running:\t%s\n%s' % ( cmd, commands.getoutput (cmd) )
def git_show (sha1, color=False):
cmd = 'git show '
if color: cmd += '--color '
return commands.getoutput (cmd + sha1)
def git_show_cdup():
'''Returns a relative path to the git project root.'''
return commands.getoutput ('git rev-parse --show-cdup')
def git_status():
'''RETURNS: A tuple of staged, unstaged and untracked files.
( array(staged), array(unstaged), array(untracked) )'''
status_lines = commands.getoutput ('git status').split ('\n')
unstaged_header_seen = False
untracked_header_seen = False
modified_header = '# Changed but not updated:'
modified_regex = re.compile('(#\tmodified:|#\tnew file:|#\tdeleted:)')
untracked_header = '# Untracked files:'
untracked_regex = re.compile ('#\t(.+)')
staged = []
unstaged = []
untracked = []
for status_line in status_lines:
if untracked_header in status_line:
untracked_header_seen = True
continue
if not untracked_header_seen:
continue
match = untracked_regex.match (status_line)
if match:
filename = match.group (1)
untracked.append (filename)
for status_line in status_lines:
if modified_header in status_line:
unstaged_header_seen = True
continue
match = modified_regex.match (status_line)
if match:
tag = match.group (0)
filename = status_line.replace (tag, '')
if unstaged_header_seen:
unstaged.append (filename.lstrip())
else:
staged.append (filename.lstrip())
return ( staged, unstaged, untracked )
-515
View File
@@ -1,515 +0,0 @@
import os
import commands
from PyQt4 import QtGui
from PyQt4.QtCore import QObject
from PyQt4.QtCore import SIGNAL
from PyQt4.QtGui import QClipboard
from PyQt4.QtGui import QDialog
from PyQt4.QtGui import QPixmap
from PyQt4.QtGui import QIcon
from PyQt4.QtGui import QListWidgetItem
from qobserver import QObserver
import ugitcmds
import ugitutils
from ugitview import GitCommandDialog
from ugitview import GitCommitBrowser
class GitController (QObserver):
'''The controller is a mediator between the model and view.
It allows for a clean decoupling between view and model classes.'''
def __init__ (self, model, view):
QObserver.__init__ (self, model, view)
# Register ourselves with the model
model.add_observer (self)
# Binds a specific model attribute to a view widget,
# and vice versa.
self.model_to_view (model, 'commitmsg', 'commitText')
# When a model attribute changes, this runs a specific action
self.add_actions (model, 'staged', self.action_staged)
self.add_actions (model, 'unstaged', self.action_unstaged)
self.add_actions (model, 'untracked', self.action_unstaged)
# Routes signals for multiple widgets to our callbacks
# defined below.
self.add_signals ('textChanged()', view.commitText)
self.add_signals ('stateChanged(int)', view.untrackedCheckBox)
self.add_signals ('pressed()',
view.rescanButton,
view.stageButton,
view.commitButton,
view.pushButton,
view.signOffButton,)
self.add_signals ('triggered()',
view.rescan,
view.commitAll,
view.commitSelected,
view.getCommitMessage,
view.stageChanged,
view.stageUntracked,
view.stageSelected,
view.unstageAll,
view.unstageSelected,
view.showDiffstat,
view.browseBranch,
view.browseOtherBranch,
view.visualizeAll,
view.visualizeCurrent,
view.exportPatches,
view.cherryPick,)
self.add_signals ('itemSelectionChanged()',
view.stagedList,
view.unstagedList,)
# Handle double-clicks in the staged/unstaged lists.
# These are vanilla signal/slots since the qobserver
# signal routing is already handling these lists' signals.
QObject.connect ( view.unstagedList,
SIGNAL ('itemDoubleClicked(QListWidgetItem*)'),
lambda (x): self.cb_stage_selected (model) )
QObject.connect ( view.stagedList,
SIGNAL ('itemDoubleClicked(QListWidgetItem*)'),
lambda (x): self.cb_unstage_selected (model) )
# These callbacks are called in response to the signals
# defined above. One property of the QObserver callback
# mechanism is that the model is passed in as the first
# argument to the callback. This allows for a single
# controller to manage multiple models, though this
# isn't used at the moment.
self.add_callbacks (model, {
# Push Buttons
'rescanButton': self.cb_rescan,
'stageButton': self.cb_stage_selected,
'signOffButton': self.cb_signoff,
'commitButton': self.cb_commit,
# Checkboxes
'untrackedCheckBox': self.cb_rescan,
# List Widgets
'stagedList': self.cb_diff_staged,
'unstagedList': self.cb_diff_unstaged,
# Menu Actions
'rescan': self.cb_rescan,
'commitAll': self.cb_commit_all,
'commitSelected': self.cb_commit_selected,
'getCommitMessage': self.cb_get_commit_msg,
'stageChanged': self.cb_stage_changed,
'stageUntracked': self.cb_stage_untracked,
'stageSelected': self.cb_stage_selected,
'unstageAll': self.cb_unstage_all,
'unstageSelected': self.cb_unstage_selected,
'showDiffstat': self.cb_show_diffstat,
'browseBranch': self.cb_browse_current,
'browseOtherBranch': self.cb_browse_other,
'visualizeCurrent': self.cb_viz_current,
'visualizeAll': self.cb_viz_all,
'exportPatches': self.cb_export_patches,
'cherryPick': self.cb_cherry_pick,
})
# chdir to the root of the git tree. This is critical
# to being able to properly use the git porcelain.
cdup = ugitcmds.git_show_cdup()
if cdup: os.chdir (cdup)
# Default to creating a new commit (i.e. not an amend commit)
view.newCommitRadio.setChecked (True)
self.__set_branch_ui_items()
self.cb_rescan (model)
#####################################################################
# MODEL ACTIONS
#####################################################################
def action_staged (self, model, *args):
'''This action is called when the model's staged list
changes. This is a thin wrapper around update_list_widget.'''
list_widget = self.view.stagedList
staged = model.get_staged()
self.__update_list_widget (list_widget, staged, True)
def action_unstaged (self, model, *args):
'''This action is called when the model's unstaged list
changes. This is a thin wrapper around update_list_widget.'''
list_widget = self.view.unstagedList
unstaged = model.get_unstaged()
self.__update_list_widget (list_widget, unstaged, False)
if self.view.untrackedCheckBox.isChecked():
untracked = model.get_untracked()
self.__update_list_widget (list_widget, untracked,
append=True,
staged=False,
untracked=True)
#####################################################################
# CALLBACKS
#####################################################################
def cb_browse_current (self, model):
print "MODEL::::::::::", model
def cb_browse_other (self, model):
print "MODEL::::::::::", model
def cb_cherry_pick (self, model, *args):
'''Starts a cherry-picking session.'''
(revs, summaries) = ugitcmds.git_log (all=True)
selection, idxs = self.__select_commits (revs, summaries)
if not selection: return
output = ugitcmds.git_cherry_pick (selection)
self.__show_command_output (output, model)
def cb_commit (self, model, *args):
'''Sets up data and calls ugitcmds.commit.'''
msg = model.get_commitmsg()
amend = self.view.amendRadio.isChecked()
commit_all = self.view.commitAllCheckBox.isChecked()
files = []
if not commit_all:
files = self.__get_selection_from_view (
self.view.stagedList,
model.get_staged() )
# Perform the commit
output = ugitcmds.git_commit (msg, amend, commit_all, files)
# Reset the commitmsg and rescan changes
if not output.startswith ('ERROR'):
model.set_commitmsg ('')
self.__show_command_output (output, model)
def cb_commit_all (self, model, *args):
'''Sets the commit-all checkbox and runs cb_commit.'''
self.view.commitAllCheckBox.setChecked (True)
self.cb_commit (model, *args)
def cb_commit_selected (self, model, *args):
'''Unsets the commit-all checkbox and runs cb_commit.'''
self.view.commitAllCheckBox.setChecked (False)
self.cb_commit (model, *args)
def cb_commit_sha1_selected (self, browser, revs):
'''This callback is called when a commit browser's
item is selected. This callback puts the current
revision sha1 into the commitText field.
This callback also puts shows the commit in the
browser's commit textedit and copies it into
the global clipboard/selection.'''
current = browser.commitList.currentRow()
item = browser.commitList.item (current)
if not item.isSelected():
browser.commitText.setText ('')
browser.revisionLine.setText ('')
return
# Get the commit's sha1 and put it in the revision line
sha1 = revs[current]
browser.revisionLine.setText (sha1)
browser.revisionLine.selectAll()
# Lookup the info for that sha1 and display it
commit_info = ugitcmds.git_show (sha1, color=True)
html = ugitutils.ansi_to_html (commit_info)
browser.commitText.setText (html)
# Copy the sha1 into the clipboard
QtGui.qApp.clipboard().setText (sha1, QClipboard.Clipboard)
QtGui.qApp.clipboard().setText (sha1, QClipboard.Selection)
def cb_diff_staged (self, model):
list_widget = self.view.stagedList
row, selected = self.__get_selected_row (list_widget)
if not selected:
self.view.displayText.setText ('')
return
filename = model.get_staged()[row]
diff = ugitcmds.git_diff (filename, staged=True)
html = ugitutils.ansi_to_html (diff)
if os.path.exists (filename):
pre = ugitutils.html_header ('Staged for commit')
else:
pre = ugitutils.html_header ('Staged for removal')
self.view.displayText.setText (pre + html)
def cb_diff_unstaged (self, model):
list_widget = self.view.unstagedList
row, selected = self.__get_selected_row (list_widget)
if not selected:
self.view.displayText.setText ('')
return
filename = (model.get_unstaged() + model.get_untracked())[row]
if os.path.isdir (filename):
pre = ugitutils.html_header ('Untracked directory')
cmd = 'ls -la %s' % ugitutils.shell_quote (filename)
html = '<pre>%s</pre>' % commands.getoutput (cmd)
self.view.displayText.setText ( pre + html )
return
if filename in model.get_unstaged():
diff = ugitcmds.git_diff (filename, staged=False)
pre = ugitutils.html_header ('Modified, unstaged')
html = ugitutils.ansi_to_html (diff)
else:
# untracked file
cmd = 'file -b %s' % ugitutils.shell_quote (filename)
file_type = commands.getoutput (cmd)
if 'binary' in file_type or 'data' in file_type:
sq_filename = ugitutils.shell_quote (filename)
cmd = 'hexdump -C %s' % sq_filename
contents = commands.getoutput (cmd)
else:
file = open (filename, 'r')
contents = ugitutils.html_encode (file.read())
file.close()
header = 'Untracked file: ' + file_type
pre = ugitutils.html_header (header)
html = '<pre>%s</pre>' % contents
self.view.displayText.setText (pre + html)
def cb_export_patches (self, model, *args):
'''Launches the commit browser and exports the selected
patches.'''
(revs, summaries) = ugitcmds.git_log ()
selection, idxs = self.__select_commits (revs, summaries)
if not selection: return
# now get the selected indices to determine whether
# a range of consecutive commits were selected
selected_range = range (idxs[0], idxs[-1] + 1)
export_range = len (idxs) > 1 and idxs == selected_range
output = ugitcmds.git_format_patch (selection, export_range)
self.__show_command_output (output)
def cb_get_commit_msg (self, model, *args):
(revs, summaries) = ugitcmds.git_log (oneline=True)
commit_msg = []
commit_lines = ugitcmds.git_show (revs[0]).split ('\n')
for idx, msg in enumerate (commit_lines):
if idx < 4: continue
msg = msg.lstrip()
if msg.startswith ('diff --git'):
commit_msg.pop()
break
commit_msg.append (msg)
model.set_commitmsg ('\n'.join (commit_msg).rstrip())
def cb_rescan (self, model, *args):
'''Populates view widgets with results from "git status."'''
# This allows us to defer notification until the
# we finish processing data
model.set_notify(False)
# Reset the staged and unstaged model lists
# NOTE: the model's unstaged list is used to
# hold both unstaged and untracked files.
model.staged = []
model.unstaged = []
model.untracked = []
# Read git status items
( staged_items,
unstaged_items,
untracked_items ) = ugitcmds.git_status()
# Gather items to be committed
for staged in staged_items:
if staged not in model.get_staged():
model.add_staged (staged)
# Gather unindexed items
for unstaged in unstaged_items:
if unstaged not in model.get_unstaged():
model.add_unstaged (unstaged)
# Gather untracked items
for untracked in untracked_items:
if untracked not in model.get_untracked():
model.add_untracked (untracked)
# Re-enable notifications and emit changes
model.set_notify(True)
model.notify_observers ('staged', 'unstaged')
def cb_show_diffstat (self, model, *args):
'''Show the diffstat from the latest commit.'''
output = ugitutils.ansi_to_html (ugitcmds.git_diff_stat())
doc = ugitutils.html_document (output)
self.__show_command_output (doc, rescan=False)
def cb_signoff (self, model, *args):
'''Adds a standard Signed-off by: tag to the end
of the current commit message.'''
msg = model.get_commitmsg()
signoff = 'Signed-off by: %s <%s>' % (
model.get_name(), model.get_email() )
if signoff not in msg:
model.set_commitmsg( '%s\n\n%s' % ( msg, signoff ) )
def cb_stage_changed (self, model, *args):
'''Stage all changed files for commit.'''
output = ugitcmds.git_add (model.get_unstaged())
self.__show_command_output (output, model)
def cb_stage_selected (self, model, *args):
'''Use "git add" to add items to the git index.
This is a thin wrapper around __apply_to_list.'''
command = ugitcmds.git_add_or_remove
widget = self.view.unstagedList
items = model.get_unstaged() + model.get_untracked()
self.__apply_to_list (command, model, widget, items)
def cb_stage_untracked (self, model, *args):
'''Stage all untracked files for commit.'''
output = ugitcmds.git_add (model.get_untracked())
self.__show_command_output (output, model)
def cb_unstage_all (self, model, *args):
'''Use "git reset" to remove all items from the git index.'''
output = ugitcmds.git_reset (model.get_staged())
self.__show_command_output (output, model)
def cb_unstage_selected (self, model, *args):
'''Use "git reset" to remove items from the git index.
This is a thin wrapper around __apply_to_list.'''
command = ugitcmds.git_reset
widget = self.view.stagedList
items = model.get_staged()
self.__apply_to_list (command, model, widget, items)
def cb_viz_all (self, model):
'''Visualizes the entire git history using gitk.'''
os.system ('gitk --all &')
def cb_viz_current (self, model):
'''Visualizes the current branch's history using gitk.'''
branch = ugitcmds.git_current_branch()
os.system ('gitk %s &' % ugitutils.shell_quote (branch))
#####################################################################
# PRIVATE HELPER METHODS
#####################################################################
def __apply_to_list (self, command, model, widget, items):
'''This is a helper method that retrieves the current
selection list, applies a command to that list,
displays a dialog showing the output of that command,
and calls cb_rescan to pickup changes.'''
apply_items = self.__get_selection_from_view (widget, items)
output = command (apply_items)
self.__show_command_output (output, model)
def __file_to_widget_item (self, filename, staged, untracked=False):
'''Given a filename, return a QListWidgetItem suitable
for adding to a QListWidget. "staged" controls whether
to use icons for the staged or unstaged list widget.'''
if staged:
icon_file = ugitutils.get_staged_icon (filename)
elif untracked:
icon_file = ugitutils.get_untracked_icon()
else:
icon_file = ugitutils.get_icon (filename)
icon = QIcon (QPixmap (icon_file))
item = QListWidgetItem()
item.setText (filename)
item.setIcon (icon)
return item
def __get_selected_row (self, list_widget):
row = list_widget.currentRow()
item = list_widget.item (row)
selected = item.isSelected()
return (row, selected)
def __get_selection_from_view (self, list_widget, items):
'''Returns an array of model items that correspond to
the selected QListWidget indices.'''
selected = []
for idx in range (list_widget.count()):
item = list_widget.item (idx)
if item.isSelected():
selected.append (items[idx])
return selected
def __select_commits (self, revs, summaries):
'''Use the GitCommitBrowser to select commits from a list.'''
if not summaries:
msg = 'ERROR: No commits exist in this branch.'''
self.__show_command_output (output=msg)
return ([],[])
browser = GitCommitBrowser (self.view)
QObject.connect ( browser.commitList,
SIGNAL ('itemSelectionChanged()'),
lambda: self.cb_commit_sha1_selected(
browser, revs) )
for summary in summaries:
browser.commitList.addItem (summary)
browser.show()
result = browser.exec_()
if result != QDialog.Accepted:
return ([],[])
list_widget = browser.commitList
selection = self.__get_selection_from_view (list_widget, revs)
if not selection: return ([],[])
# also return the selected index numbers
index_nums = range (len (revs))
idxs = self.__get_selection_from_view (list_widget, index_nums)
return (selection, idxs)
def __set_branch_ui_items (self):
'''Sets up items that mention the current branch name.'''
current_branch = ugitcmds.git_current_branch()
menu_text = 'Browse ' + current_branch + ' branch'
self.view.browseBranch.setText (menu_text)
status_text = 'Current branch: ' + current_branch
self.view.statusBar().showMessage (status_text)
def __show_command_output (self, output, model=None, rescan=True):
'''Shows output in a GitCommandDialog and optionally
rescans for changes.'''
dialog = GitCommandDialog (self.view, output=output)
dialog.show()
dialog.exec_()
if rescan and model: self.cb_rescan (model)
def __update_list_widget (self, list_widget, items,
staged, untracked=False, append=False):
'''A helper method to populate a QListWidget with the
contents of modelitems.'''
if not append:
list_widget.clear()
for item in items:
qitem = self.__file_to_widget_item (item,
staged, untracked)
list_widget.addItem( qitem )
-15
View File
@@ -1,15 +0,0 @@
import commands
from model import Model
def get_config(key):
return commands.getoutput('git config --get "%s"' % key)
class GitModel(Model):
def __init__ (self): Model.__init__(self, {
'commitmsg': '',
'staged': [],
'unstaged': [],
'untracked': [],
'name': get_config('user.name'),
'email': get_config('user.email'),
})
-429
View File
@@ -1,429 +0,0 @@
#!/usr/bin/env python
import os
import re
import commands
KNOWN_FILE_TYPES = {
'ascii c': 'c.png',
'python': 'script.png',
'ruby': 'script.png',
'shell': 'script.png',
'perl': 'script.png',
'java': 'script.png',
'assembler': 'binary.png',
'binary': 'binary.png',
'byte': 'binary.png',
'image': 'image.png',
}
ICONSDIR = os.path.join (os.path.dirname (__file__), 'icons')
def ident_file_type (filename):
'''Returns an icon based on the contents of filename.'''
if os.path.exists (filename):
quoted_filename = shell_quote (filename)
fileinfo = commands.getoutput('file -b %s' % quoted_filename)
for filetype, iconname in KNOWN_FILE_TYPES.iteritems():
if filetype in fileinfo.lower():
return iconname
else:
return 'removed.png'
# Fallback for modified files of an unknown type
return 'generic.png'
def get_icon (filename):
'''Returns the full path to an icon file corresponding to
filename's contents.'''
icon_file = ident_file_type (filename)
return os.path.join (ICONSDIR, icon_file)
def get_staged_icon (filename):
'''Special-case method for staged items. These are only
ever 'staged' and 'removed' items in the staged list.'''
if os.path.exists (filename):
return os.path.join (ICONSDIR, 'staged.png')
else:
return os.path.join (ICONSDIR, 'removed.png')
def get_untracked_icon():
return os.path.join (ICONSDIR, 'untracked.png')
def shell_quote (*inputs):
'''Quote strings so that they can be suitably martialled
off to the shell. This method supports POSIX sh syntax.
This is crucial to properly handle command line arguments
with spaces, quotes, double-quotes, etc.'''
regex = re.compile ('[^\w!%+,\-./:@^]')
quote_regex = re.compile ("((?:'\\''){2,})")
ret = []
for input in inputs:
if not input:
continue
if '\x00' in input:
raise AssertionError, ('No way to quote strings '
'containing null (\\000) bytes')
# = does need quoting else in command position it's a
# program-local environment setting
match = regex.search (input)
if match:
# ' -> '\''
input = input.replace ("'", "'\\''")
# make multiple ' in a row look simpler
# '\'''\'''\'' -> '"'''"'
quote_match = quote_regex.match (input)
if quote_match:
quotes = match.group (1)
input.replace (quotes,
("'" * (len(quotes)/4)) + "\"'")
input = "'%s'" % input
if input.startswith ("''"):
input = input.lstrip ("''")
if input.endswith ("''"):
input = input.rstrip ("''")
ret.append (input)
return ' '.join (ret)
ANSI_BACKGROUND_COLOR = 41
ANSI_TABLE = {
'1': 'grey',
'30': 'black',
'31': 'red',
'32': 'green',
'33': 'yellow',
'34': 'blue',
'35': 'magenta',
'36': 'cyan',
'37': 'white',
}
def html_document (html_fragment):
return '<body>%s</body>' % html_fragment
def ansi_to_html (ansi):
'''Converts a block of text into an equivalent html fragment.'''
html = []
regex = re.compile ('(.*?)\x1b\[(\d*)m')
for line in ansi.split ('\n'):
linecopy = str (html_encode(line))
match = regex.match (linecopy)
tagged = False
while match:
start, end = match.span()
prefix = match.group (1)
middle = match.group (2)
postfix = linecopy[end:]
if middle in ANSI_TABLE:
color = ANSI_TABLE[middle]
middle = '<span style="color: %s">' % color
tagged = True
elif middle == str (ANSI_BACKGROUND_COLOR):
middle = '<span style="background-color:red">'
tagged = True
else:
if tagged:
middle = '</span>'
else:
middle = ''
linecopy = str (prefix + middle + postfix)
match = regex.match (linecopy)
html.append (linecopy)
return '<br />\n'.join (html)
def html_header (header):
return '''
<p style="color: black;
background-color: yellow">
%s
</p>''' % header
def html_encode (ascii):
html = []
ANSI = [ '\x1b', '[' ]
for char in ascii:
if char == '\t':
html.append ( '&nbsp;' * 8 )
continue
if char not in ANSI and not char.isalnum():
html.append (ENTITIES[char])
else:
html.append (char)
return ''.join (html)
# Keep this at the bottom of the file since it's generated output.
# Generated by html2py.pl from HTML::Entities on Wed Dec 5 16:28:55 PST 2007
ENTITIES = {
chr(0) : '&#0;',
chr(1) : '&#1;',
chr(2) : '&#2;',
chr(3) : '&#3;',
chr(4) : '&#4;',
chr(5) : '&#5;',
chr(6) : '&#6;',
chr(7) : '&#7;',
chr(8) : '&#8;',
chr(9) : '&#9;',
chr(10) : '&#10;',
chr(11) : '&#11;',
chr(12) : '&#12;',
chr(13) : '&#13;',
chr(14) : '&#14;',
chr(15) : '&#15;',
chr(16) : '&#16;',
chr(17) : '&#17;',
chr(18) : '&#18;',
chr(19) : '&#19;',
chr(20) : '&#20;',
chr(21) : '&#21;',
chr(22) : '&#22;',
chr(23) : '&#23;',
chr(24) : '&#24;',
chr(25) : '&#25;',
chr(26) : '&#26;',
chr(27) : '&#27;',
chr(28) : '&#28;',
chr(29) : '&#29;',
chr(30) : '&#30;',
chr(31) : '&#31;',
chr(32) : '&#32;',
chr(33) : '&#33;',
chr(34) : '&quot;',
chr(35) : '&#35;',
chr(36) : '&#36;',
chr(37) : '&#37;',
chr(38) : '&amp;',
chr(39) : '&#39;',
chr(40) : '&#40;',
chr(41) : '&#41;',
chr(42) : '&#42;',
chr(43) : '&#43;',
chr(44) : '&#44;',
chr(45) : '&#45;',
chr(46) : '&#46;',
chr(47) : '&#47;',
chr(48) : '&#48;',
chr(49) : '&#49;',
chr(50) : '&#50;',
chr(51) : '&#51;',
chr(52) : '&#52;',
chr(53) : '&#53;',
chr(54) : '&#54;',
chr(55) : '&#55;',
chr(56) : '&#56;',
chr(57) : '&#57;',
chr(58) : '&#58;',
chr(59) : '&#59;',
chr(60) : '&lt;',
chr(61) : '&#61;',
chr(62) : '&gt;',
chr(63) : '&#63;',
chr(64) : '&#64;',
chr(65) : '&#65;',
chr(66) : '&#66;',
chr(67) : '&#67;',
chr(68) : '&#68;',
chr(69) : '&#69;',
chr(70) : '&#70;',
chr(71) : '&#71;',
chr(72) : '&#72;',
chr(73) : '&#73;',
chr(74) : '&#74;',
chr(75) : '&#75;',
chr(76) : '&#76;',
chr(77) : '&#77;',
chr(78) : '&#78;',
chr(79) : '&#79;',
chr(80) : '&#80;',
chr(81) : '&#81;',
chr(82) : '&#82;',
chr(83) : '&#83;',
chr(84) : '&#84;',
chr(85) : '&#85;',
chr(86) : '&#86;',
chr(87) : '&#87;',
chr(88) : '&#88;',
chr(89) : '&#89;',
chr(90) : '&#90;',
chr(91) : '&#91;',
chr(92) : '&#92;',
chr(93) : '&#93;',
chr(94) : '&#94;',
chr(95) : '&#95;',
chr(96) : '&#96;',
chr(97) : '&#97;',
chr(98) : '&#98;',
chr(99) : '&#99;',
chr(100) : '&#100;',
chr(101) : '&#101;',
chr(102) : '&#102;',
chr(103) : '&#103;',
chr(104) : '&#104;',
chr(105) : '&#105;',
chr(106) : '&#106;',
chr(107) : '&#107;',
chr(108) : '&#108;',
chr(109) : '&#109;',
chr(110) : '&#110;',
chr(111) : '&#111;',
chr(112) : '&#112;',
chr(113) : '&#113;',
chr(114) : '&#114;',
chr(115) : '&#115;',
chr(116) : '&#116;',
chr(117) : '&#117;',
chr(118) : '&#118;',
chr(119) : '&#119;',
chr(120) : '&#120;',
chr(121) : '&#121;',
chr(122) : '&#122;',
chr(123) : '&#123;',
chr(124) : '&#124;',
chr(125) : '&#125;',
chr(126) : '&#126;',
chr(127) : '&#127;',
chr(128) : '&#128;',
chr(129) : '&#129;',
chr(130) : '&#130;',
chr(131) : '&#131;',
chr(132) : '&#132;',
chr(133) : '&#133;',
chr(134) : '&#134;',
chr(135) : '&#135;',
chr(136) : '&#136;',
chr(137) : '&#137;',
chr(138) : '&#138;',
chr(139) : '&#139;',
chr(140) : '&#140;',
chr(141) : '&#141;',
chr(142) : '&#142;',
chr(143) : '&#143;',
chr(144) : '&#144;',
chr(145) : '&#145;',
chr(146) : '&#146;',
chr(147) : '&#147;',
chr(148) : '&#148;',
chr(149) : '&#149;',
chr(150) : '&#150;',
chr(151) : '&#151;',
chr(152) : '&#152;',
chr(153) : '&#153;',
chr(154) : '&#154;',
chr(155) : '&#155;',
chr(156) : '&#156;',
chr(157) : '&#157;',
chr(158) : '&#158;',
chr(159) : '&#159;',
chr(160) : '&nbsp;',
chr(161) : '&iexcl;',
chr(162) : '&cent;',
chr(163) : '&pound;',
chr(164) : '&curren;',
chr(165) : '&yen;',
chr(166) : '&brvbar;',
chr(167) : '&sect;',
chr(168) : '&uml;',
chr(169) : '&copy;',
chr(170) : '&ordf;',
chr(171) : '&laquo;',
chr(172) : '&not;',
chr(173) : '&shy;',
chr(174) : '&reg;',
chr(175) : '&macr;',
chr(176) : '&deg;',
chr(177) : '&plusmn;',
chr(178) : '&sup2;',
chr(179) : '&sup3;',
chr(180) : '&acute;',
chr(181) : '&micro;',
chr(182) : '&para;',
chr(183) : '&middot;',
chr(184) : '&cedil;',
chr(185) : '&sup1;',
chr(186) : '&ordm;',
chr(187) : '&raquo;',
chr(188) : '&frac14;',
chr(189) : '&frac12;',
chr(190) : '&frac34;',
chr(191) : '&iquest;',
chr(192) : '&Agrave;',
chr(193) : '&Aacute;',
chr(194) : '&Acirc;',
chr(195) : '&Atilde;',
chr(196) : '&Auml;',
chr(197) : '&Aring;',
chr(198) : '&AElig;',
chr(199) : '&Ccedil;',
chr(200) : '&Egrave;',
chr(201) : '&Eacute;',
chr(202) : '&Ecirc;',
chr(203) : '&Euml;',
chr(204) : '&Igrave;',
chr(205) : '&Iacute;',
chr(206) : '&Icirc;',
chr(207) : '&Iuml;',
chr(208) : '&ETH;',
chr(209) : '&Ntilde;',
chr(210) : '&Ograve;',
chr(211) : '&Oacute;',
chr(212) : '&Ocirc;',
chr(213) : '&Otilde;',
chr(214) : '&Ouml;',
chr(215) : '&times;',
chr(216) : '&Oslash;',
chr(217) : '&Ugrave;',
chr(218) : '&Uacute;',
chr(219) : '&Ucirc;',
chr(220) : '&Uuml;',
chr(221) : '&Yacute;',
chr(222) : '&THORN;',
chr(223) : '&szlig;',
chr(224) : '&agrave;',
chr(225) : '&aacute;',
chr(226) : '&acirc;',
chr(227) : '&atilde;',
chr(228) : '&auml;',
chr(229) : '&aring;',
chr(230) : '&aelig;',
chr(231) : '&ccedil;',
chr(232) : '&egrave;',
chr(233) : '&eacute;',
chr(234) : '&ecirc;',
chr(235) : '&euml;',
chr(236) : '&igrave;',
chr(237) : '&iacute;',
chr(238) : '&icirc;',
chr(239) : '&iuml;',
chr(240) : '&eth;',
chr(241) : '&ntilde;',
chr(242) : '&ograve;',
chr(243) : '&oacute;',
chr(244) : '&ocirc;',
chr(245) : '&otilde;',
chr(246) : '&ouml;',
chr(247) : '&divide;',
chr(248) : '&oslash;',
chr(249) : '&ugrave;',
chr(250) : '&uacute;',
chr(251) : '&ucirc;',
chr(252) : '&uuml;',
chr(253) : '&yacute;',
chr(254) : '&thorn;',
chr(255) : '&yuml;',
}
-38
View File
@@ -1,38 +0,0 @@
import os
from PyQt4 import QtCore, QtGui
from PyQt4.QtCore import SIGNAL
from ugitWindow import Ui_ugitWindow
from ugitCommandDialog import Ui_ugitCommandDialog
from ugitCommitBrowser import Ui_ugitCommitBrowser
class GitView (Ui_ugitWindow, QtGui.QMainWindow):
'''The main ugit interface.'''
def __init__ (self, parent=None, autosetup=True):
QtGui.QMainWindow.__init__ (self, parent)
Ui_ugitWindow.__init__ (self)
if autosetup:
self.setupUi (self)
class GitCommandDialog (Ui_ugitCommandDialog, QtGui.QDialog):
'''A simple dialog to display command output.'''
def __init__ (self, parent=None, output=None, autosetup=True):
QtGui.QDialog.__init__ (self, parent)
Ui_ugitCommandDialog.__init__ (self)
if autosetup:
self.setupUi (self)
if output is not None:
self.set_command_output (output)
def set_command_output (self, output):
self.commandText.setText (output)
class GitCommitBrowser (Ui_ugitCommitBrowser, QtGui.QDialog):
'''A dialog to display commits in for selection.'''
def __init__ (self, parent=None, autosetup=True):
QtGui.QDialog.__init__ (self, parent)
Ui_ugitCommitBrowser.__init__ (self)
if autosetup:
self.setupUi (self)
# Make the commit list slighty larger than the
# commit message widget
self.splitter.setSizes ([ 50, 200 ])
-5
View File
@@ -1,5 +0,0 @@
#!/usr/bin/env python
def build(bld):
pyqt = bld.create_obj ('py')
pyqt.inst_var = 'PYMODS_UGIT'
pyqt.find_sources_in_dirs ('.')
-27
View File
@@ -1,27 +0,0 @@
#!/usr/bin/env perl
use HTML::Entities;
my $date = `date`;
chomp $date;
print "# Generated by html2py.pl from HTML::Entities on $date\n";
print "ENTITIES = {\n";
for my $k (sort keys %HTML::Entities::char2entity) {
my $int = ord($k);
if ( $int > 255 ) {
# These are either:
# - outside of ord()'s 0-255 range
# - used in ANSI color sequences handled by
# ugitutils` ansi_to_html.
next;
}
my $v = $HTML::Entities::char2entity{$k};
print "\t";
print "chr($int)";
print "\t:\t";
print "'$v',\n";
}
print "}\n";
+2
View File
@@ -0,0 +1,2 @@
[install]
prefix: ~
Executable
+131
View File
@@ -0,0 +1,131 @@
#!/usr/bin/env python
import re
import os
import sys
import stat
from glob import glob
from distutils.core import setup
from distutils.command import build_scripts
build_scripts.first_line_re = re.compile('^should not match$')
from cola import version
from cola import utils
def main():
# ensure readable files
old_mask = os.umask(0022)
if sys.argv[1] in ['install', 'build']:
__check_python_version()
__check_git_version()
__check_pyqt_version()
__build_views() # pyuic4: .ui -> .py
__build_translations() # msgfmt: .po -> .qm
try:
version.write_builtin_version()
__run_setup()
finally:
version.delete_builtin_version()
# restore the old mask
os.umask(old_mask)
def __run_setup():
setup(name = 'cola',
version = version.version,
license = 'GPLv2',
author = 'David Aguilar',
author_email = 'davvid@gmail.com',
url = 'http://cola.tuxfamily.org/',
description = 'GIT Cola',
long_description = 'A highly caffeinated GIT GUI',
scripts = ['bin/git-cola', 'bin/git-difftool'],
packages = ['cola', 'cola.gui', 'cola.views', 'cola.controllers'],
data_files = [
__app_path('share/cola/qm', '*.qm'),
__app_path('share/cola/icons', '*.png'),
__app_path('share/cola/styles', '*.qss'),
__app_path('share/cola/styles/images', '*.png'),
__app_path('share/applications', '*.desktop'),
__app_path('share/doc/cola', '*.txt'),
])
def __app_path(dirname, entry):
if '/' in entry:
return (dirname, glob(entry))
else:
return (dirname, glob(os.path.join(dirname, entry)))
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
def __check_min_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
def __check_python_version():
"""Check the minimum Python version
"""
pyver = '.'.join(map(lambda x: str(x), sys.version_info))
if not __check_min_version(version.python_min_ver, pyver):
print >> sys.stderr, 'Python version %s or newer required. Found %s' \
% (version.python_min_ver, pyver)
sys.exit(1)
def __check_git_version():
"""Check the minimum GIT version
"""
gitver = utils.run_cmd('git', '--version').split()[2]
if not __check_min_version(version.git_min_ver, gitver):
print >> sys.stderr, 'GIT version %s or newer required. Found %s' \
% (version.git_min_ver, gitver)
sys.exit(1)
def __check_pyqt_version():
"""Check the minimum PYQT version
"""
pyqtver = utils.run_cmd('pyuic4', '--version').split()[-1]
if not __check_min_version(version.pyqt_min_ver, pyqtver):
print >> sys.stderr, 'PYQT version %s or newer required. Found %s' \
% (version.pyqt_min_ver, pyqtver)
sys.exit(1)
def __dirty(src, dst):
if not os.path.exists(dst):
return True
srcstat = os.stat(src)
dststat = os.stat(dst)
return srcstat[stat.ST_MTIME] > dststat[stat.ST_MTIME]
def __build_views():
print 'running build_views'
views = os.path.join('cola', 'gui')
sources = glob('ui/*.ui')
for src in sources:
dst = os.path.join(views, os.path.basename(src)[:-3] + '.py')
if __dirty(src, dst):
print '\tpyuic4 -x %s -o %s' % (src, dst)
utils.run_cmd('pyuic4', '-x', src, '-o', dst)
def __build_translations():
print 'running build_translations'
sources = glob('share/cola/po/*.po')
for src in sources:
dst = os.path.join('share', 'cola', 'qm',
os.path.basename(src)[:-3] + '.qm')
if __dirty(src, dst):
print '\tmsgfmt --qt %s -o %s' % (src, dst)
utils.run_cmd('msgfmt', '--qt', src, '-o', dst)
main()
+10
View File
@@ -0,0 +1,10 @@
[Desktop Entry]
Name=Cola
Comment=Highly caffeinated git gui
Exec=git-cola
Icon=/usr/share/cola/icons/git.png
StartupNotify=true
Terminal=false
Type=Application
Categories=Development;
X-KDE-SubstituteUID=false
+177
View File
@@ -0,0 +1,177 @@
The following license applies to all icons except staged.png.
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
+5
View File
@@ -0,0 +1,5 @@
ACKNOWLEDGEMENTS:
staged.png is from the "slick" KDE icon set.
All other icons are from the Apache httpd server icon set.
See the COPYING file for more details.

Before

Width:  |  Height:  |  Size: 316 B

After

Width:  |  Height:  |  Size: 316 B

Before

Width:  |  Height:  |  Size: 308 B

After

Width:  |  Height:  |  Size: 308 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 301 B

Before

Width:  |  Height:  |  Size: 284 B

After

Width:  |  Height:  |  Size: 284 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 B

Before

Width:  |  Height:  |  Size: 383 B

After

Width:  |  Height:  |  Size: 383 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 B

Before

Width:  |  Height:  |  Size: 315 B

After

Width:  |  Height:  |  Size: 315 B

Before

Width:  |  Height:  |  Size: 299 B

After

Width:  |  Height:  |  Size: 299 B

Before

Width:  |  Height:  |  Size: 539 B

After

Width:  |  Height:  |  Size: 539 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 B

Before

Width:  |  Height:  |  Size: 316 B

After

Width:  |  Height:  |  Size: 316 B

+2
View File
@@ -0,0 +1,2 @@
*.msg
*~
+3
View File
@@ -0,0 +1,3 @@
These are from the git-gui sources.
Please see git-gui's po/README file for more details.
TODO: todo
+1999
View File
File diff suppressed because it is too large Load Diff
+1992
View File
File diff suppressed because it is too large Load Diff
+185
View File
@@ -0,0 +1,185 @@
# Translation of git-gui glossary to German
# Copyright (C) 2007 Shawn Pearce, et al.
# This file is distributed under the same license as the git package.
# Christian Stimming <stimming@tuhh.de>, 2007
#
msgid ""
msgstr ""
"Project-Id-Version: git-gui glossary\n"
"POT-Creation-Date: 2007-10-19 21:43+0200\n"
"PO-Revision-Date: 2007-10-20 15:24+0200\n"
"Last-Translator: Christian Stimming <stimming@tuhh.de>\n"
"Language-Team: German \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#. "English Definition (Dear translator: This file will never be visible to the user! It should only serve as a tool for you, the translator. Nothing more.)"
msgid ""
"English Term (Dear translator: This file will never be visible to the user!)"
msgstr ""
"Deutsche Übersetzung.\n"
"Andere deutsche SCM:\n"
" http://tortoisesvn.net/docs/release/TortoiseSVN_de/index.html und http://"
"tortoisesvn.tigris.org/svn/tortoisesvn/trunk/Languages/Tortoise_de.po "
"(username=guest, password empty, gut),\n"
" http://msdn.microsoft.com/de-de/library/ms181038(vs.80).aspx (MS Visual "
"Source Safe, kommerziell),\n"
" http://cvsbook.red-bean.com/translations/german/Kap_06.html "
"(mittelmäßig),\n"
" http://tortoisecvs.cvs.sourceforge.net/tortoisecvs/po/TortoiseCVS/de_DE.po?"
"view=markup (mittelmäßig),\n"
" http://rapidsvn.tigris.org/svn/rapidsvn/trunk/src/locale/de/rapidsvn.po "
"(username=guest, password empty, schlecht)"
#. ""
msgid "amend"
msgstr "nachbessern (ergänzen)"
#. ""
msgid "annotate"
msgstr "annotieren"
#. "A 'branch' is an active line of development."
msgid "branch [noun]"
msgstr "Zweig"
#. ""
msgid "branch [verb]"
msgstr "verzweigen"
#. ""
msgid "checkout [noun]"
msgstr ""
"Arbeitskopie (Erstellung einer Arbeitskopie; Auscheck? Ausspielung? Abruf? "
"Source Safe: Auscheckvorgang)"
#. "The action of updating the working tree to a revision which was stored in the object database."
msgid "checkout [verb]"
msgstr ""
"Arbeitskopie erstellen; Zweig umstellen [checkout a branch] (auschecken? "
"ausspielen? abrufen? Source Safe: auschecken)"
#. ""
msgid "clone [verb]"
msgstr "kopieren"
#. "A single point in the git history."
msgid "commit [noun]"
msgstr ""
"Version; Eintragung; Änderung (Buchung?, Eintragung?, Übertragung?, "
"Sendung?, Übergabe?, Einspielung?, Ablagevorgang?)"
#. "The action of storing a new snapshot of the project's state in the git history."
msgid "commit [verb]"
msgstr ""
"eintragen (TortoiseSVN: übertragen; Source Safe: einchecken; senden?, "
"übergeben?, einspielen?, einpflegen?, ablegen?)"
#. ""
msgid "diff [noun]"
msgstr "Vergleich (Source Safe: Unterschiede)"
#. ""
msgid "diff [verb]"
msgstr "vergleichen"
#. "A fast-forward is a special type of merge where you have a revision and you are merging another branch's changes that happen to be a descendant of what you have."
msgid "fast forward merge"
msgstr "Schnellzusammenführung"
#. "Fetching a branch means to get the branch's head from a remote repository, to find out which objects are missing from the local object database, and to get them, too."
msgid "fetch"
msgstr "anfordern (holen?)"
#. "A collection of files. The index is a stored version of your working tree."
msgid "index (in git-gui: staging area)"
msgstr "Bereitstellung"
#. "A successful merge results in the creation of a new commit representing the result of the merge."
msgid "merge [noun]"
msgstr "Zusammenführung"
#. "To bring the contents of another branch into the current branch."
msgid "merge [verb]"
msgstr "zusammenführen"
#. ""
msgid "message"
msgstr "Beschreibung (Meldung?, Nachricht?; Source Safe: Kommentar)"
#. "Deletes all stale tracking branches under <name>. These stale branches have already been removed from the remote repository referenced by <name>, but are still locally available in 'remotes/<name>'."
msgid "prune"
msgstr "entfernen"
#. "Pulling a branch means to fetch it and merge it."
msgid "pull"
msgstr "übernehmen (ziehen?)"
#. "Pushing a branch means to get the branch's head ref from a remote repository, and ... (well, can someone please explain it for mere mortals?)"
msgid "push"
msgstr "versenden (ausliefern? hochladen? verschicken? schieben?)"
#. ""
msgid "redo"
msgstr "wiederholen"
#. "An other repository ('remote'). One might have a set of remotes whose branches one tracks."
msgid "remote"
msgstr "Andere Archive (Gegenseite?, Entfernte?, Server?)"
#. "A collection of refs (?) together with an object database containing all objects which are reachable from the refs... (oops, you've lost me here. Again, please an explanation for mere mortals?)"
msgid "repository"
msgstr "Projektarchiv"
#. ""
msgid "reset"
msgstr "zurücksetzen (zurückkehren?)"
#. ""
msgid "revert"
msgstr "revidieren"
#. "A particular state of files and directories which was stored in the object database."
msgid "revision"
msgstr "Version (TortoiseSVN: Revision; Source Safe: Version)"
#. ""
msgid "sign off"
msgstr "abzeichnen (gegenzeichnen?, freizeichnen?, absegnen?)"
#. ""
msgid "staging area"
msgstr "Bereitstellung"
#. ""
msgid "status"
msgstr "Status"
#. "A ref pointing to a tag or commit object"
msgid "tag [noun]"
msgstr "Markierung"
#. ""
msgid "tag [verb]"
msgstr "markieren"
#. "A regular git branch that is used to follow changes from another repository."
msgid "tracking branch"
msgstr "Übernahmezweig"
#. ""
msgid "undo"
msgstr "rückgängig"
#. ""
msgid "update"
msgstr "aktualisieren"
#. ""
msgid "verify"
msgstr "überprüfen"
#. "The tree of actual checked out files."
msgid "working copy, working tree"
msgstr "Arbeitskopie"
+184
View File
@@ -0,0 +1,184 @@
# Translation of git-gui glossary to Italian
# Copyright (C) 2007 Shawn Pearce, et al.
# This file is distributed under the same license as the git package.
# Christian Stimming <stimming@tuhh.de>, 2007
#
msgid ""
msgstr ""
"Project-Id-Version: git-gui glossary\n"
"POT-Creation-Date: 2007-10-19 21:43+0200\n"
"PO-Revision-Date: 2007-10-10 15:24+0200\n"
"Last-Translator: Michele Ballabio <barra_cuda@katamail.com>\n"
"Language-Team: Italian \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#. "English Definition (Dear translator: This file will never be visible to the user! It should only serve as a tool for you, the translator. Nothing more.)"
msgid ""
"English Term (Dear translator: This file will never be visible to the user!)"
msgstr ""
"Traduzione italiana.\n"
"Altri SCM in italiano:\n"
" http://tortoisesvn.tigris.org/svn/tortoisesvn/trunk/Languages/Tortoise_it."
"po (username=guest, password empty),\n"
" http://tortoisecvs.cvs.sourceforge.net/tortoisecvs/po/TortoiseCVS/it_IT.po?"
"view=markup ,\n"
" http://rapidsvn.tigris.org/svn/rapidsvn/trunk/src/locale/it_IT/rapidsvn.po "
"(username=guest, password empty)"
#. ""
msgid "amend"
msgstr "correggere, correzione"
#. ""
msgid "annotate"
msgstr "annotare, annotazione"
#. "A 'branch' is an active line of development."
msgid "branch [noun]"
msgstr "ramo, diramazione, ramificazione"
#. ""
msgid "branch [verb]"
msgstr "creare ramo, ramificare, diramare"
#. ""
msgid "checkout [noun]"
msgstr "attivazione, checkout, revisione attiva, prelievo (TortoiseCVS)?"
#. "The action of updating the working tree to a revision which was stored in the object database."
msgid "checkout [verb]"
msgstr ""
"attivare, effettuare un checkout, attivare revisione, prelevare "
"(TortoiseCVS), ritirare (TSVN)?"
#. ""
msgid "clone [verb]"
msgstr "clonare"
#. "A single point in the git history."
msgid "commit [noun]"
msgstr "revisione, commit, deposito (TortoiseCVS), invio (TSVN)?"
#. "The action of storing a new snapshot of the project's state in the git history."
msgid "commit [verb]"
msgstr ""
"creare una nuova revisione, archiviare, effettuare un commit, depositare "
"(nel server), fare un deposito (TortoiseCVS), inviare (TSVN)?"
#. ""
msgid "diff [noun]"
msgstr "differenza, confronto, comparazione, raffronto"
#. ""
msgid "diff [verb]"
msgstr "confronta, mostra le differenze"
#. "A fast-forward is a special type of merge where you have a revision and you are merging another branch's changes that happen to be a descendant of what you have."
msgid "fast forward merge"
msgstr "fusione in 'fast-forward', fusione in avanti veloce"
#. "Fetching a branch means to get the branch's head from a remote repository, to find out which objects are missing from the local object database, and to get them, too."
msgid "fetch"
msgstr "recuperare, prelevare, prendere da, recuperare (TSVN)"
#. "A collection of files. The index is a stored version of your working tree."
msgid "index (in git-gui: staging area)"
msgstr "indice"
#. "A successful merge results in the creation of a new commit representing the result of the merge."
msgid "merge [noun]"
msgstr "fusione, unione"
#. "To bring the contents of another branch into the current branch."
msgid "merge [verb]"
msgstr "effettuare la fusione, unire, fondere, eseguire la fusione"
#. ""
msgid "message"
msgstr "messaggio, commento"
#. "Deletes all stale tracking branches under <name>. These stale branches have already been removed from the remote repository referenced by <name>, but are still locally available in 'remotes/<name>'."
msgid "prune"
msgstr "potatura"
#. "Pulling a branch means to fetch it and merge it."
msgid "pull"
msgstr ""
"prendi (recupera) e fondi (unisci)? (in pratica una traduzione di fetch + "
"merge)"
#. "Pushing a branch means to get the branch's head ref from a remote repository, and ... (well, can someone please explain it for mere mortals?)"
msgid "push"
msgstr "propaga"
#. ""
msgid "redo"
msgstr "ripeti, rifai"
#. "An other repository ('remote'). One might have a set of remotes whose branches one tracks."
msgid "remote"
msgstr "remoto"
#. "A collection of refs (?) together with an object database containing all objects which are reachable from the refs... (oops, you've lost me here. Again, please an explanation for mere mortals?)"
msgid "repository"
msgstr "archivio, repository, database? deposito (rapidsvn)?"
#. ""
msgid "reset"
msgstr "ripristinare, annullare, azzerare, ripristinare"
#. ""
msgid "revert"
msgstr ""
"annullare, inverti (rapidsvn), ritorna allo stato precedente, annulla le "
"modifiche della revisione"
#. "A particular state of files and directories which was stored in the object database."
msgid "revision"
msgstr "revisione (TortoiseSVN)"
#. ""
msgid "sign off"
msgstr "sign off, firma"
#. ""
msgid "staging area"
msgstr ""
"area di preparazione, zona di preparazione, modifiche in preparazione? "
"modifiche in allestimento?"
#. ""
msgid "status"
msgstr "stato"
#. "A ref pointing to a tag or commit object"
msgid "tag [noun]"
msgstr "etichetta, etichettatura (TortoiseCVS)"
#. ""
msgid "tag [verb]"
msgstr "etichettare"
#. "A regular git branch that is used to follow changes from another repository."
msgid "tracking branch"
msgstr ""
"duplicato locale di ramo remoto, ramo in 'tracking', ramo inseguitore? ramo "
"di {inseguimento,allineamento,rilevamento,puntamento}?"
#. ""
msgid "undo"
msgstr "annulla"
#. ""
msgid "update"
msgstr "aggiornamento, aggiornare"
#. ""
msgid "verify"
msgstr "verifica, verificare"
#. "The tree of actual checked out files."
msgid "working copy, working tree"
msgstr "directory di lavoro, copia di lavoro"
+170
View File
@@ -0,0 +1,170 @@
# Translation of git-gui glossary to Simplified Chinese
# Copyright (C) 2007 Shawn Pearce, et al.
# This file is distributed under the same license as the git package.
# Xudong Guan <xudong.guan@gmail.com> and the zh-kernel.org mailing list, 2007
#
msgid ""
msgstr ""
"Project-Id-Version: git-gui glossary\n"
"PO-Revision-Date: 2007-07-23 22:07+0200\n"
"Last-Translator: Xudong Guan <xudong.guan@gmail.com>\n"
"Language-Team: Simplified Chinese \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#. "English Definition (Dear translator: This file will never be visible to the user! It should only serve as a tool for you, the translator. Nothing more.)"
msgid ""
"English Term (Dear translator: This file will never be visible to the user!)"
msgstr "注:这个文件是为了帮助翻译人员统一名词术语。最终用户不会关心这个文件。"
#. ""
#. amend指用户修改最近一次commit的操作,修订?修改?修正?
#. [WANG Cong]: 根据我的了解,这个词似乎翻译成“修订”多一些。“修正”也可以,“修改”再次之。
#. [ZHANG Le]: 修订,感觉一般指对一些大型出版物的大规模升级,比如修订新华字典
# 修正,其实每次amend的结果也不一定就是最后结果,说不定还需要修改。所以不
# 如就叫修改
msgid "amend"
msgstr "修订"
#. ""
#. git annotate 文件名:用来标注文件的每一行在什么时候被谁最后修改。
#. [WANG Cong]: "标记"一般是mark。;)
#. [ZHANG Le]: 标注,或者干脆用原意:注解,或注释
msgid "annotate"
msgstr "标注"
#. "A 'branch' is an active line of development."
msgid "branch [noun]"
msgstr "分支"
#. ""
msgid "branch [verb]"
msgstr "建立分支"
#. ""
#. [WANG Cong]: 网上有人翻译成“检出”,我感觉更好一些,毕竟把check的意思翻译出来了。
#. [ZHNAG Le]: 提取吧,提取分支/版本
#. [rae l]: 签出。subversion软件中的大多词汇已有翻译,既然git与subversion同是SCM管理,可以参考同类软件的翻译也不错。
msgid "checkout [noun]"
msgstr "签出"
#. "The action of updating the working tree to a revision which was stored in the object database."
msgid "checkout [verb]"
msgstr "签出"
#. "A single point in the git history."
msgid "commit [noun]"
msgstr "提交"
#. "The action of storing a new snapshot of the project's state in the git history."
msgid "commit [verb]"
msgstr "提交"
#. ""
#. 差异?差别?
#. [ZHANG Le]: 个人感觉差别更加中性一些
msgid "diff [noun]"
msgstr "差别"
#. ""
msgid "diff [verb]"
msgstr "比较"
#. "A fast-forward is a special type of merge where you have a revision and you are merging another branch's changes that happen to be a descendant of what you have."
msgid "fast forward merge"
msgstr "快进式合并"
#. "Fetching a branch means to get the branch's head from a remote repository, to find out which objects are missing from the local object database, and to get them, too."
#. 获取?取得?下载?更新?注意和update的区分
msgid "fetch"
msgstr "获取"
#. "A collection of files. The index is a stored version of your working tree."
#. index是working tree和repository之间的缓存
msgid "index (in git-gui: staging area)"
msgstr "工作缓存?"
#. "A successful merge results in the creation of a new commit representing the result of the merge."
msgid "merge [noun]"
msgstr "合并"
#. "To bring the contents of another branch into the current branch."
msgid "merge [verb]"
msgstr "合并"
#. ""
#. message是指commit中的文字信息
msgid "message"
msgstr "描述"
#. "Pulling a branch means to fetch it and merge it."
msgid "pull"
msgstr "获取+合并"
#. "Pushing a branch means to get the branch's head ref from a remote repository, and ... (well, can someone please explain it for mere mortals?)"
msgid "push"
msgstr "推入"
#. ""
msgid "redo"
msgstr "重做"
#. "A collection of refs (?) together with an object database containing all objects which are reachable from the refs... (oops, you've lost me here. Again, please an explanation for mere mortals?)"
msgid "repository"
msgstr "仓库"
#. ""
msgid "reset"
msgstr "重置"
#. ""
msgid "revert"
msgstr "恢复"
#. "A particular state of files and directories which was stored in the object database."
msgid "revision"
msgstr "版本"
#. ""
msgid "sign off"
msgstr "签名"
#. ""
#. 似乎是git-gui里面显示的本次提交的文件清单区域
msgid "staging area"
msgstr "提交暂存区"
#. ""
msgid "status"
msgstr "状态"
#. "A ref pointing to a tag or commit object"
msgid "tag [noun]"
msgstr "标签"
#. ""
msgid "tag [verb]"
msgstr "添加标签"
#. "A regular git branch that is used to follow changes from another repository."
msgid "tracking branch"
msgstr "跟踪分支"
#. ""
msgid "undo"
msgstr "撤销"
#. ""
msgid "update"
msgstr "更新。注意和fetch的区分"
#. ""
msgid "verify"
msgstr "验证"
#. "The tree of actual checked out files."
#. "工作副本?工作区域?工作目录"
#. [LI Yang]: 当前副本, 当前源码树?
msgid "working copy, working tree"
msgstr "工作副本,工作源码树"
+2025
View File
File diff suppressed because it is too large Load Diff
+1994
View File
File diff suppressed because it is too large Load Diff
+1969
View File
File diff suppressed because it is too large Load Diff
+1973
View File
File diff suppressed because it is too large Load Diff
+1980
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1
View File
@@ -0,0 +1 @@
*.qm
+282
View File
@@ -0,0 +1,282 @@
/* Colors for use by the syntax highlighters */
StandardView {
qproperty-color_add: rgba(0, 255, 0, 64);
qproperty-color_remove: rgba(255, 0, 0, 64);
qproperty-color_begin: rgba(0, 128, 255, 255);
qproperty-color_header: rgba(255, 255, 64, 255);
qproperty-color_stat_info: rgba(255, 128, 0, 255);
qproperty-color_stat_add: rgba(32, 255, 32, 255);
qproperty-color_stat_info: rgba(32, 32, 255, 255);
qproperty-color_stat_remove: rgba(255, 32, 32, 255);
qproperty-color_emphasis: rgba(255, 255, 0, 255);
qproperty-color_info: rgba(255, 0, 255, 255);
qproperty-color_date: rgba(0, 128, 255, 255);
}
QWidget {
color: #DFDFDF;
background-color: #202020;
selection-background-color: #8C3500; /*qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #8C3500, stop:1 #9D4611);*/
selection-color: #FFFFFF;
alternate-background-color: #262626;
}
QWidget:disabled {
color: #444444;
selection-background-color: #808080;
}
QPushButton:!flat {
background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #323232, stop:1 #222222);
border: 1px solid #323232;
border-radius: 3px;
padding: 2px;
}
QPushButton:hover:!flat {
background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #383838, stop:1 #242424);
}
QPushButton:pressed:!flat {
background-color: #141414;
}
QToolButton {
background-color: #202020;
border: 1px solid #202020;
padding: 1px;
}
QToolButton:hover {
background-color: #262626;
border: 1px solid #262626;
border-radius: 3px;
}
QAbstractScrollArea {
background-color: #242424;
border: 1px solid #303030;
}
QTextEdit, QLineEdit {
background-color: #282828;
}
QMenuBar, QStatusBar {
background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #343434, stop:1 #202020);
border: 0px;
}
QMenu {
background-color: #303030;
border: 1px solid #484848;
border-radius: 5px;
}
QStatusBar::item {
border: 0px;
border-radius: 3px;
}
QMenu::item:disabled {
color: #444444;
background-color: transparent;
}
QMenu::separator {
background-color: #262626;
height: 1px;
margin-left: 15px;
margin-right:15px;
}
QSizeGrip {
image: url(images:size_grip.png);
background: transparent;
}
QToolTip {
background-color: #5D2300;
border: 1px;
}
QHeaderView {
background-color: #343434;
}
QHeaderView::down-arrow {
image: url(images:arrow-down.png);
}
QHeaderView::up-arrow {
image: url(images:arrow-up.png);
}
QSlider::handle:horizontal {
image: url(images:arrow-down.png);
}
QProgressBar {
border: 2px solid grey;
border-radius: 5px;
}
QProgressBar::chunk {
background-color: #05B8CC;
width: 20px;
}
QCheckBox:indicator {
width: 14px;
height: 16px;
}
QCheckBox:indicator:checked {
image: url(images:checkbox_checked.png);
}
QCheckBox:indicator:unchecked {
image: url(images:checkbox_unchecked.png);
}
QRadioButton:indicator {
width: 14px;
height: 16px;
}
QRadioButton:indicator:checked {
image: url(images:checkbox_checked.png);
}
QRadioButton:indicator:unchecked {
image: url(images:checkbox_unchecked.png);
}
/* Vertical Scrollbar */
QScrollBar::handle:vertical {
background-image: url(images:scrollbar_vertical.png);
background-repeat: repeat-y;
background-position: center;
min-height: 20px;
}
QScrollBar::handle:vertical:hover {
background-image: url(images:scrollbar_vertical_hover.png);
background-repeat: repeat-y;
background-position: center;
}
QScrollBar:vertical {
border: 1px solid;
width: 13px;
margin: 12px 0px 12px 0px;
background: black;
}
QScrollBar::add-line:vertical {
image: url(images:scrollbar_vertical_subline.png);
subcontrol-position: bottom center;
subcontrol-origin: margin;
padding: 0px;
}
QScrollBar::add-line:vertical:hover {
image: url(images:scrollbar_vertical_subline_hover.png);
}
QScrollBar::sub-line:vertical:hover {
image: url(images:scrollbar_vertical_addline_hover.png);
}
QScrollBar::sub-line:vertical {
image: url(images:scrollbar_vertical_addline.png);
subcontrol-position: top center;
subcontrol-origin: margin;
padding: 0px;
}
QScrollBar:up-arrow:vertical, QScrollBar::down-arrow:vertical {
background: none;
}
QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {
background: black;
}
/* Horizontal Scrollbars */
QScrollBar::handle:horizontal {
background-image: url(images:scrollbar_horizontal.png);
background-repeat: repeat-x;
background-position: center;
min-width: 20px;
}
QScrollBar::handle:horizontal:hover {
background-image: url(images:scrollbar_horizontal_hover.png);
background-repeat: repeat-x;
background-position: center;
}
QScrollBar:horizontal {
border: 1px solid;
height: 13px;
margin: 0px 12px 0px 12px;
background: black;
}
QScrollBar::add-line:horizontal {
image: url(images:scrollbar_horizontal_addline.png);
subcontrol-position: right;
subcontrol-origin: margin;
padding: 0px;
}
QScrollBar::sub-line:horizontal {
image: url(images:scrollbar_horizontal_subline.png);
subcontrol-position: left;
subcontrol-origin: margin;
padding: 0px;
}
QScrollBar::add-line:horizontal:hover {
image: url(images:scrollbar_horizontal_addline_hover.png);
}
QScrollBar::sub-line:horizontal:hover {
image: url(images:scrollbar_horizontal_subline_hover.png);
}
QScrollBar:left-arrow:horizontal, QScrollBar::right-arrow:horizontal {
background: none;
}
QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal {
background: black;
}
/*
RED: c83232
DARK GREY: 323232
BUTTON: 2a2a2a
WINDOW: 0a0a0a
HIGHLIGHT: 8c3500
HIGHLIGHT TEXT: FFFFFF
DISABLED HIGHLIGHT TEXT: 808080
BASE, WINDOW: 202020
LIGHT: 282828
MIDLIGHT: 373737
DARK: 555555
MID: 464646
SHADOW: 646464
*/
Binary file not shown.

After

Width:  |  Height:  |  Size: 329 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 561 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 632 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 313 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 B

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