├── CONTRIBUTING ├── CONTRIBUTORS ├── LICENSE ├── README.MD ├── core ├── .classpath ├── .gitignore ├── .project ├── .settings │ └── org.eclipse.jdt.core.prefs ├── META-INF │ └── MANIFEST.MF ├── bin │ └── .gitignore ├── build.properties ├── pom.xml └── src │ └── com │ └── google │ └── appraise │ └── eclipse │ └── core │ ├── AppraiseConnectorPlugin.java │ ├── AppraisePluginReviewClient.java │ ├── AppraisePluginUtils.java │ ├── AppraiseRepositoryConnector.java │ ├── AppraiseReviewTaskSchema.java │ ├── AppraiseReviewsTaskDataHandler.java │ ├── AppraiseTaskMapper.java │ └── client │ ├── data │ ├── Review.java │ ├── ReviewComment.java │ ├── ReviewCommentLocation.java │ ├── ReviewCommentLocationRange.java │ ├── ReviewCommentResult.java │ ├── ReviewResult.java │ └── User.java │ └── git │ ├── AppraiseGitReviewClient.java │ ├── GitClientException.java │ ├── GitNoteWriter.java │ └── JgitUtils.java ├── doc └── screenshots │ ├── New-Line-Comment-Menu.png │ ├── Review-Display-With-Comments.png │ └── Review-Display.png ├── feature ├── .gitignore ├── .project ├── build.properties ├── feature.xml └── pom.xml ├── pom.xml ├── site ├── .gitignore ├── .project ├── index.html ├── pom.xml ├── site.xml └── web │ ├── site.css │ └── site.xsl └── ui ├── .classpath ├── .gitignore ├── .project ├── .settings └── org.eclipse.jdt.core.prefs ├── META-INF └── MANIFEST.MF ├── bin └── .gitignore ├── build.properties ├── icons ├── appraise-icon.gif ├── greencheck.png └── overlay-appraise.gif ├── plugin.xml ├── pom.xml └── src └── com └── google └── appraise └── eclipse └── ui ├── AppraiseConnectorUi.java ├── AppraiseReviewMarkerView.java ├── AppraiseReviewsQueryPage.java ├── AppraiseUiPlugin.java ├── Author.java ├── DateTime.java ├── EditCommentDialog.java ├── Resolved.java ├── ReviewMarkerAttributes.java ├── contextmenus ├── ReviewMarkerContributionFactory.java ├── ReviewMarkerMenuContribution.java ├── TextEditorContextContributionFactory.java └── TextEditorContextMenuContribution.java ├── editor ├── AppraiseDiffViewerPart.java ├── AppraiseReviewAttributePart.java ├── AppraiseReviewTaskActivationListener.java ├── AppraiseReviewTaskEditorPage.java ├── AppraiseReviewTaskEditorPageFactory.java ├── AppraiseTaskEditorCommentPart.java ├── CommentAttributeEditor.java ├── CommitAttributeEditor.java ├── DiffAttributeEditor.java └── ReviewMarkerManager.java └── wizard └── AppraiseRepositorySettingsPage.java /CONTRIBUTING: -------------------------------------------------------------------------------- 1 | Want to contribute? Great! First, read this page (including the small print at the end). 2 | 3 | ### Before you contribute 4 | Before we can use your code, you must sign the 5 | [Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual?csw=1) 6 | (CLA), which you can do online. The CLA is necessary mainly because you own the 7 | copyright to your changes, even after your contribution becomes part of our 8 | codebase, so we need your permission to use and distribute your code. We also 9 | need to be sure of various other things—for instance that you'll tell us if you 10 | know that your code infringes on other people's patents. You don't have to sign 11 | the CLA until after you've submitted your code for review and a member has 12 | approved it, but you must do it before we can put your code into our codebase. 13 | Before you start working on a larger contribution, you should get in touch with 14 | us first through the issue tracker with your idea so that we can help out and 15 | possibly guide you. Coordinating up front makes it much easier to avoid 16 | frustration later on. 17 | 18 | ### Code reviews 19 | All submissions, including submissions by project members, require review. We 20 | use Github pull requests for this purpose. 21 | 22 | ### The small print 23 | Contributions made by corporations are covered by a different agreement than 24 | the one above, the Software Grant and Corporate Contributor License Agreement. -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | # People who have agreed to one of the CLAs and can contribute patches. 2 | # The AUTHORS file lists the copyright holders; this file 3 | # lists people. For example, Google employees are listed here 4 | # but not in AUTHORS, because Google holds the copyright. 5 | # 6 | # https://developers.google.com/open-source/cla/individual 7 | # https://developers.google.com/open-source/cla/corporate 8 | # 9 | # Names should be added to this file as: 10 | # Name 11 | Scott McMaster 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Eclipse Public License - v 1.0 2 | 3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 4 | 5 | 1. DEFINITIONS 6 | 7 | "Contribution" means: 8 | 9 | a) in the case of the initial Contributor, the initial code and documentation distributed under this Agreement, and 10 | b) in the case of each subsequent Contributor: 11 | i) changes to the Program, and 12 | ii) additions to the Program; 13 | where such changes and/or additions to the Program originate from and are distributed by that particular Contributor. A Contribution 'originates' from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include additions to the Program which: (i) are separate modules of software distributed in conjunction with the Program under their own license agreement, and (ii) are not derivative works of the Program. 14 | "Contributor" means any person or entity that distributes the Program. 15 | 16 | "Licensed Patents" mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. 17 | 18 | "Program" means the Contributions distributed in accordance with this Agreement. 19 | 20 | "Recipient" means anyone who receives the Program under this Agreement, including all Contributors. 21 | 22 | 2. GRANT OF RIGHTS 23 | 24 | a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, distribute and sublicense the Contribution of such Contributor, if any, and such derivative works, in source code and object code form. 25 | b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in source code and object code form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder. 26 | c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program. 27 | d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. 28 | 3. REQUIREMENTS 29 | 30 | A Contributor may choose to distribute the Program in object code form under its own license agreement, provided that: 31 | 32 | a) it complies with the terms and conditions of this Agreement; and 33 | b) its license agreement: 34 | i) effectively disclaims on behalf of all Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose; 35 | ii) effectively excludes on behalf of all Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits; 36 | iii) states that any provisions which differ from this Agreement are offered by that Contributor alone and not by any other party; and 37 | iv) states that source code for the Program is available from such Contributor, and informs licensees how to obtain it in a reasonable manner on or through a medium customarily used for software exchange. 38 | When the Program is made available in source code form: 39 | 40 | a) it must be made available under this Agreement; and 41 | b) a copy of this Agreement must be included with each copy of the Program. 42 | Contributors may not remove or alter any copyright notices contained within the Program. 43 | 44 | Each Contributor must identify itself as the originator of its Contribution, if any, in a manner that reasonably allows subsequent Recipients to identify the originator of the Contribution. 45 | 46 | 4. COMMERCIAL DISTRIBUTION 47 | 48 | Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor ("Commercial Contributor") hereby agrees to defend and indemnify every other Contributor ("Indemnified Contributor") against any losses, damages and costs (collectively "Losses") arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense. 49 | 50 | For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages. 51 | 52 | 5. NO WARRANTY 53 | 54 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED 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. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement , including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations. 55 | 56 | 6. DISCLAIMER OF LIABILITY 57 | 58 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 59 | 60 | 7. GENERAL 61 | 62 | If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. 63 | 64 | If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed. 65 | 66 | All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. 67 | 68 | Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to distribute the Program (including its Contributions) under the new version. Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved. 69 | 70 | This Agreement is governed by the laws of the State of New York and the intellectual property laws of the United States of America. No party to this Agreement will bring a legal action under this Agreement more than one year after the cause of action arose. Each party waives its rights to a jury trial in any resulting litigation. 71 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # Google Appraise Eclipse Plugin 2 | 3 | Git Appraise is a code review system that uses git-notes as a data store. The Google Appraise Eclipse Plugin supports an Appraise-based code reveiw workflow inside Eclipse. It is implemented as a Mylyn Task Repository connector and leverages the Egit plugin. 4 | 5 | ## Screenshots 6 | 7 | Displaying a single review, with details and highlighted diffs for the changed files: 8 | ![Displaying a single review](./doc/screenshots/Review-Display.png?raw=true) 9 | 10 | Adding an inline comment to the current review: 11 | ![Adding an inline comment](/doc/screenshots/New-Line-Comment-Menu.png?raw=true) 12 | 13 | Displaying the comments in a review: 14 | ![Displaying the comments in a review](/doc/screenshots/Review-Display-With-Comments.png?raw=true) 15 | 16 | ## Links 17 | 18 | * [Git Appraise](https://github.com/google/git-appraise) -- for the Git Appraise command-line tool and more information about Git Appraise in general. 19 | 20 | ## User documentation 21 | 22 | ### Prerequisites 23 | 24 | * Install [Egit](http://www.eclipse.org/egit/download/). 25 | * Install the Appraise Eclipse Plugin. For now, this means using the update site archive file from one of [the releases](https://github.com/google/git-appraise-eclipse/releases). 26 | * Help | Install New Software... 27 | * Add... 28 | * Archive... 29 | * Browse to the site.zip file you downloaded. 30 | * OK. 31 | * Turn off "Group items by category" if it is selected. 32 | * Choose "Appraise" and Next, Finish. 33 | * Note that this will fail if you did not already install Egit. 34 | * In the Git perspective in Eclipse, add a repository for the project you want to work with. 35 | * Import or create an Eclipse project connected to the Git repository. *None of what follows will work otherwise.* 36 | 37 | ### Adding an Appraise Task Repository 38 | * Window | Show View | Mylyn | Task Repositories. 39 | * Click on "Add a Task Repository...". 40 | * Select "Appraise Reviews". 41 | * Under "Server", you should see an item for each Git-connected Eclipse project in your workspace. Choose the one you want. Verify that your User ID is correct for that Git repo and click "Finish". 42 | * You will now have the option to add a query. 43 | 44 | ### Creating a review query 45 | * In the Task List view, select (from the toolbar or right-click) "New Query...". 46 | * Select the Appraise repository and click "Next". 47 | * Enter a title for your query. (This will show up in the Task List). 48 | * You may choose to filter for reviews where you are the requestor or reviewer, or filter by a specific review commit hash prefix. 49 | * You can refresh the query (to see new reviews from your collaborators, for example) with "Synchronize" or "Synchronize Automatically". 50 | 51 | ### Requesting a code review 52 | Note that the Appraise workflow model expects you to request a review from a working branch, and the Eclipse Plugin assumes that you currently have that branch checked out. (You can confirm this by looking at the project node name in the Package Explorer.) 53 | * In the Task List view, select (from the toolbar or right-click) "New Task...". 54 | * Select the Appraise repository and click "Finish". 55 | * The Task Editor appears: 56 | * The Review Ref should be pre-filled with the name of your current branch. 57 | * The Target Ref (where your review will be submitted) defaults to refs/heads/master. You can change this if necessary. 58 | * You should select some code reviewers by filling in their User ID's as a comma-separated list in the "Reviewers" text box. 59 | * Your commited changes are listed, and you can view the diffs. 60 | * When you are satisfied, request the review by clicking the "Submit" button for the task. (Underneath the hood, this will create git-notes on the refs/notes/devtools/reviews ref and push them.) 61 | 62 | ### Reviewing code 63 | The code review workflow for Appraise in Eclipse is based around Mylyn's task activation model. 64 | * Right-click a review task in the Task List and choose "Activate". 65 | * You will be given the option to checkout the review branch. 66 | * Double-click the task to open the task editor where you can: 67 | * See the changes with diffs. 68 | * See the comments. 69 | * Make new comments. 70 | * Add or remove reviewers. 71 | * Window | Show View | Other | Other and select "Appraise Review Tasks" to open the "Appraise Review Tasks" view. 72 | * There, you can view all the review comments and, if there is a location associated with the comment, double-click to open the relevant source file. 73 | * Open a source file. 74 | * If there are any comments from the active review associated with that source file, they will appear as markers in the gutter. 75 | * Right-click any existing marker and select a comment from the "Appraise Review Comments" menu to reply to the comment. 76 | * Right-click anywhere in the source editor and select "Appraise Review Comments" to create a new comment associated with the file the file, the location clicked, or the overall review. 77 | * After making all comments, return to the task editor to: 78 | * Submit comments with the "Submit" button. (This has the effect of merging git-notes for the review commit on the refs/notes/devtools/discuss ref.) 79 | 80 | ## Releases 81 | Releases will be tagged in the repository and an announcement will be made on our Github page. We have no strict timetable but aim to do this every three months or whenever a more urgent need arises. 82 | 83 | ## Roadmap 84 | * Improved UI for creating and responding to comments (e.g. inline with the source). 85 | * Smoother Mylyn integration. 86 | 87 | ## How to develop 88 | 89 | Import the following projects into Eclipse: 90 | * com.google.appraise.eclipse.core 91 | * com.google.appraise.eclipse.ui 92 | * feature 93 | * site 94 | 95 | To debug, right-click the "core" project and Run As...Eclipse Application. 96 | 97 | To build the update site archive (site/target/site_assembly.zip): 98 | mvn package 99 | 100 | Please send pull requests. 101 | -------------------------------------------------------------------------------- /core/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /core/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /core/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | com.google.appraise.eclipse.core 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.pde.ManifestBuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.pde.SchemaBuilder 20 | 21 | 22 | 23 | 24 | 25 | org.eclipse.pde.PluginNature 26 | org.eclipse.jdt.core.javanature 27 | 28 | 29 | -------------------------------------------------------------------------------- /core/.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 3 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 4 | org.eclipse.jdt.core.compiler.compliance=1.7 5 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error 6 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error 7 | org.eclipse.jdt.core.compiler.source=1.7 8 | -------------------------------------------------------------------------------- /core/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Bundle-ManifestVersion: 2 3 | Bundle-Name: Appraise Connector Core 4 | Bundle-SymbolicName: com.google.appraise.eclipse.core;singleton:=true 5 | Bundle-Version: 1.0.0.qualifier 6 | Bundle-Activator: com.google.appraise.eclipse.core.AppraiseConnectorPlugin 7 | Bundle-Vendor: Google 8 | Require-Bundle: org.eclipse.core.runtime, 9 | org.eclipse.team.core;bundle-version="3.7.0", 10 | org.eclipse.egit.core;bundle-version="3.7.1", 11 | org.eclipse.core.resources;bundle-version="3.9.1", 12 | org.eclipse.jgit;bundle-version="3.7.1", 13 | org.eclipse.mylyn.tasks.core;bundle-version="3.9.0", 14 | org.apache.commons.codec, 15 | org.eclipse.swt, 16 | org.eclipse.jface;bundle-version="3.11.0" 17 | Bundle-RequiredExecutionEnvironment: JavaSE-1.7 18 | Bundle-ActivationPolicy: lazy 19 | Export-Package: com.google.appraise.eclipse.core, 20 | com.google.appraise.eclipse.core.client.data, 21 | com.google.appraise.eclipse.core.client.git 22 | Import-Package: com.google.gson 23 | -------------------------------------------------------------------------------- /core/bin/.gitignore: -------------------------------------------------------------------------------- 1 | /com/ 2 | -------------------------------------------------------------------------------- /core/build.properties: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Copyright (c) 2015 Google and others. 3 | # All rights reserved. This program and the accompanying materials 4 | # are made available under the terms of the Eclipse Public License v1.0 5 | # which accompanies this distribution, and is available at 6 | # http://www.eclipse.org/legal/epl-v10.html 7 | # 8 | # Contributors: 9 | # Scott McMaster - initial implementation 10 | ############################################################################### 11 | source.. = src/ 12 | output.. = bin/ 13 | bin.includes = META-INF/,\ 14 | . 15 | additional.bundles = org.apache.commons.codec 16 | -------------------------------------------------------------------------------- /core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.google.appraise 8 | eclipse-plugin 9 | 0.0.1-SNAPSHOT 10 | 11 | com.google.appraise 12 | com.google.appraise.eclipse.core 13 | 1.0.0-SNAPSHOT 14 | eclipse-plugin 15 | 16 | 17 | 18 | org.apache.maven.plugins 19 | maven-compiler-plugin 20 | 3.3 21 | 22 | 1.7 23 | 1.7 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /core/src/com/google/appraise/eclipse/core/AppraiseConnectorPlugin.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Google and others. 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Eclipse Public License v1.0 5 | * which accompanies this distribution, and is available at 6 | * http://www.eclipse.org/legal/epl-v10.html 7 | * 8 | * Contributors: 9 | * Scott McMaster - initial implementation 10 | *******************************************************************************/ 11 | package com.google.appraise.eclipse.core; 12 | 13 | import org.eclipse.core.runtime.IStatus; 14 | import org.eclipse.core.runtime.Plugin; 15 | import org.eclipse.core.runtime.Status; 16 | import org.osgi.framework.BundleContext; 17 | 18 | /** 19 | * The Appraise plugin bundle. 20 | */ 21 | public class AppraiseConnectorPlugin extends Plugin { 22 | /** 23 | * The global plugin instance. 24 | */ 25 | public static AppraiseConnectorPlugin plugin; 26 | 27 | /** 28 | * The plugin id. 29 | */ 30 | public static final String PLUGIN_ID = "com.google.appraise.eclipse.core"; 31 | 32 | /** 33 | * The type of the connector. 34 | */ 35 | public static final String CONNECTOR_KIND = "com.google.appraise"; 36 | 37 | /** 38 | * Connector query attribute for the user as review requestor. 39 | */ 40 | public static final String QUERY_REQUESTER = PLUGIN_ID + ".requestor"; 41 | 42 | /** 43 | * Connector query attribute for the user as reviewer. 44 | */ 45 | public static final String QUERY_REVIEWER = PLUGIN_ID + ".reviewer"; 46 | 47 | /** 48 | * Connector query attribute for the review commit hash prefix. 49 | */ 50 | public static final String QUERY_REVIEW_COMMIT_PREFIX = PLUGIN_ID + ".reviewcommitprefix"; 51 | 52 | private static BundleContext context; 53 | 54 | static BundleContext getContext() { 55 | return context; 56 | } 57 | 58 | @Override 59 | public void start(BundleContext bundleContext) throws Exception { 60 | super.start(bundleContext); 61 | plugin = this; 62 | } 63 | 64 | @Override 65 | public void stop(BundleContext bundleContext) throws Exception { 66 | plugin = null; 67 | super.stop(bundleContext); 68 | } 69 | 70 | /** 71 | * Returns the shared instance. 72 | */ 73 | public static AppraiseConnectorPlugin getDefault() { 74 | return plugin; 75 | } 76 | 77 | public static void logError(final String message, final Throwable throwable) { 78 | getDefault().getLog().log(new Status(IStatus.ERROR, PLUGIN_ID, message, throwable)); 79 | } 80 | 81 | public static void logWarning(final String message, final Throwable throwable) { 82 | getDefault().getLog().log(new Status(IStatus.WARNING, PLUGIN_ID, message, throwable)); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /core/src/com/google/appraise/eclipse/core/AppraisePluginReviewClient.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Google and others. 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Eclipse Public License v1.0 5 | * which accompanies this distribution, and is available at 6 | * http://www.eclipse.org/legal/epl-v10.html 7 | * 8 | * Contributors: 9 | * Scott McMaster - initial implementation 10 | *******************************************************************************/ 11 | package com.google.appraise.eclipse.core; 12 | 13 | import com.google.appraise.eclipse.core.client.data.Review; 14 | import com.google.appraise.eclipse.core.client.data.ReviewComment; 15 | import com.google.appraise.eclipse.core.client.data.ReviewCommentResult; 16 | import com.google.appraise.eclipse.core.client.data.ReviewResult; 17 | import com.google.appraise.eclipse.core.client.data.User; 18 | import com.google.appraise.eclipse.core.client.git.AppraiseGitReviewClient; 19 | import com.google.appraise.eclipse.core.client.git.GitClientException; 20 | 21 | import org.eclipse.jface.dialogs.MessageDialog; 22 | import org.eclipse.jgit.diff.DiffEntry; 23 | import org.eclipse.jgit.lib.Repository; 24 | import org.eclipse.jgit.revwalk.RevCommit; 25 | import org.eclipse.mylyn.tasks.core.TaskRepository; 26 | import org.eclipse.mylyn.tasks.core.data.TaskAttribute; 27 | import org.eclipse.mylyn.tasks.core.data.TaskData; 28 | import org.eclipse.swt.widgets.Display; 29 | 30 | import java.util.ArrayList; 31 | import java.util.Collections; 32 | import java.util.Comparator; 33 | import java.util.List; 34 | import java.util.Map; 35 | import java.util.concurrent.atomic.AtomicBoolean; 36 | 37 | /** 38 | * Talks to the git notes to read Appraise reviews and their comments from 39 | * a specific {@link TaskRepository}. 40 | */ 41 | public class AppraisePluginReviewClient { 42 | private static final String CREATE_REVIEW_WARNING = 43 | "Creating a new review will push the code to be reviewed from the review branch, " 44 | + "sync git-notes, merge if necessary, and push a new git-notes commit " 45 | + "on the refs/notes/devtools/review ref."; 46 | 47 | private static final String UPDATE_REVIEW_WARNING = 48 | "Updating a review with new values and comments will " 49 | + "sync git-notes, merge if necessary, and push a new git-notes commit " 50 | + "on the refs/notes/devtools/review ref."; 51 | 52 | private static final String WRITE_COMMENTS_WARNING = 53 | "Writing comments will sync git-notes, merge if necessary, and push a new git-notes commit " 54 | + "on the refs/notes/devtools/discuss ref."; 55 | 56 | private Repository gitRepo; 57 | 58 | private AppraiseGitReviewClient gitClient; 59 | 60 | private User currentUser; 61 | 62 | public AppraisePluginReviewClient(TaskRepository repository) throws GitClientException { 63 | this.gitRepo = AppraisePluginUtils.getGitRepoForRepository(repository); 64 | if (this.gitRepo == null) { 65 | throw new GitClientException( 66 | "No git repository connected to " + repository.getRepositoryUrl()); 67 | } 68 | this.gitClient = new AppraiseGitReviewClient(this.gitRepo); 69 | 70 | String currentUserName = gitRepo.getConfig().getString("user", null, "name"); 71 | String currentUserEmail = gitRepo.getConfig().getString("user", null, "email"); 72 | this.currentUser = new User(currentUserName, currentUserEmail); 73 | } 74 | 75 | /** 76 | * Retrieves all the reviews in the current project's repository. 77 | */ 78 | public List listReviews() { 79 | try { 80 | Map reviews = gitClient.listReviews(); 81 | List results = new ArrayList<>(); 82 | for (Map.Entry reviewEntry : reviews.entrySet()) { 83 | results.add(new ReviewResult(reviewEntry.getKey(), currentUser, reviewEntry.getValue())); 84 | } 85 | Collections.sort(results, new Comparator() { 86 | @Override 87 | public int compare(ReviewResult first, ReviewResult second) { 88 | return (int) (second.getReview().getTimestamp() - first.getReview().getTimestamp()); 89 | } 90 | }); 91 | return results; 92 | } catch (GitClientException e) { 93 | AppraiseConnectorPlugin.logError("Error loading reviews", e); 94 | return null; 95 | } 96 | } 97 | 98 | /** 99 | * Retrieves a specific review from the git notes. Returns null if not found. 100 | */ 101 | public ReviewResult getReview(String hash) { 102 | try { 103 | Review review = gitClient.getReview(hash); 104 | return new ReviewResult(hash, currentUser, review); 105 | } catch (GitClientException e) { 106 | AppraiseConnectorPlugin.logError("Failed to load review " + hash, e); 107 | return null; 108 | } 109 | } 110 | 111 | /** 112 | * Gets all the comments for a specific review by hash. 113 | */ 114 | public List listCommentsForReview(String hash) { 115 | List comments = new ArrayList<>(); 116 | try { 117 | Map commentsData = gitClient.listCommentsForReview(hash); 118 | for (Map.Entry commentData : commentsData.entrySet()) { 119 | comments.add(new ReviewCommentResult(commentData.getKey(), commentData.getValue())); 120 | } 121 | } catch (GitClientException e) { 122 | AppraiseConnectorPlugin.logError("Error loading domments for " + hash, e); 123 | return null; 124 | } 125 | return comments; 126 | } 127 | 128 | /** 129 | * Writes a comment to the specified review by taking comment text out of the 130 | * given task attribute. 131 | * @param taskId Is the review commit hash in our model. 132 | * @param newComments The comment to append inside a task attribute. 133 | * @return whether the comment was written out or not. 134 | */ 135 | public boolean writeComment(String taskId, TaskAttribute newComments) { 136 | if (!displayWriteWarning(WRITE_COMMENTS_WARNING)) { 137 | return false; 138 | } 139 | try { 140 | gitClient.writeComment(taskId, newComments.getValue()); 141 | } catch (GitClientException e) { 142 | AppraiseConnectorPlugin.logError("Error writing comment for " + taskId, e); 143 | return false; 144 | } 145 | return true; 146 | } 147 | 148 | /** 149 | * Writes a comment to the specified review. 150 | * @param taskId Is the review commit hash in our model. 151 | * @param comment The comment to append. 152 | * @return whether the comment was written out or not. 153 | */ 154 | public boolean writeComment(String taskId, ReviewComment comment) { 155 | if (!displayWriteWarning(WRITE_COMMENTS_WARNING)) { 156 | return false; 157 | } 158 | try { 159 | gitClient.writeComment(taskId, comment); 160 | } catch (GitClientException e) { 161 | AppraiseConnectorPlugin.logError("Error writing comment for " + taskId, e); 162 | return false; 163 | } 164 | return true; 165 | } 166 | 167 | /** 168 | * Writes a {@link Review} to the git notes. 169 | * @return the new review's hash. 170 | */ 171 | public String writeReview(String reviewCommitHash, Review review) throws GitClientException { 172 | if (!displayWriteWarning(CREATE_REVIEW_WARNING)) { 173 | return null; 174 | } 175 | return gitClient.createReview(reviewCommitHash, review); 176 | } 177 | 178 | /** 179 | * Checks an existing review to potentially be updated, and write a new comment if given. 180 | * @return whether or not the review was updated. 181 | */ 182 | public boolean updateReview(String reviewCommitHash, Review review, String newComment) { 183 | if (!displayWriteWarning(UPDATE_REVIEW_WARNING)) { 184 | return false; 185 | } 186 | try { 187 | gitClient.updateReviewWithComment(reviewCommitHash, review, newComment); 188 | } catch (GitClientException e) { 189 | AppraiseConnectorPlugin.logError("Error updating review " + reviewCommitHash, e); 190 | return false; 191 | } 192 | return true; 193 | } 194 | 195 | /** 196 | * Gets the commit that we will write review notes and comments to. 197 | */ 198 | public RevCommit getReviewCommit(String reviewBranch, String targetBranch) 199 | throws GitClientException { 200 | return gitClient.getReviewCommit(reviewBranch, targetBranch); 201 | } 202 | 203 | /** 204 | * Gets the diff between review and target branches. 205 | */ 206 | public List getReviewDiffs(String reviewBranch, String targetBranch) 207 | throws GitClientException { 208 | return gitClient.calculateBranchDiffs(targetBranch, reviewBranch); 209 | } 210 | 211 | /** 212 | * Returns whether or not the given review has been submitted. Conventionally, 213 | * this means that the review commit is an ancestor of the target ref. 214 | */ 215 | public boolean isReviewSubmitted(ReviewResult review) throws GitClientException { 216 | if (review.getReview().getTargetRef() == null || review.getReview().getTargetRef().isEmpty()) { 217 | return false; 218 | } 219 | return gitClient.areAncestorDescendent(review.getHash(), review.getReview().getTargetRef()); 220 | } 221 | 222 | /** 223 | * Confirms that the repository is in a valid state to request a code review. 224 | */ 225 | public boolean canRequestReview(TaskData taskData) { 226 | String reviewRef = 227 | taskData.getRoot() 228 | .getAttribute(AppraiseReviewTaskSchema.getDefault().REVIEW_REF.getKey()) 229 | .getValue(); 230 | String targetRef = 231 | taskData.getRoot() 232 | .getAttribute(AppraiseReviewTaskSchema.getDefault().TARGET_REF.getKey()) 233 | .getValue(); 234 | return gitClient.canRequestReviewOnReviewRef(reviewRef, targetRef); 235 | } 236 | 237 | private boolean displayWriteWarning(final String message) { 238 | final AtomicBoolean result = new AtomicBoolean(false); 239 | Display.getDefault().syncExec(new Runnable() { 240 | @Override 241 | public void run() { 242 | result.set(MessageDialog.openConfirm(null, "Appraise Review Plugin", message)); 243 | } 244 | }); 245 | return result.get(); 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /core/src/com/google/appraise/eclipse/core/AppraisePluginUtils.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Google and others. 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Eclipse Public License v1.0 5 | * which accompanies this distribution, and is available at 6 | * http://www.eclipse.org/legal/epl-v10.html 7 | * 8 | * Contributors: 9 | * Scott McMaster - initial implementation 10 | *******************************************************************************/ 11 | package com.google.appraise.eclipse.core; 12 | 13 | import org.eclipse.core.resources.IProject; 14 | import org.eclipse.core.resources.ResourcesPlugin; 15 | import org.eclipse.egit.core.GitProvider; 16 | import org.eclipse.jgit.lib.Repository; 17 | import org.eclipse.mylyn.tasks.core.TaskRepository; 18 | import org.eclipse.team.core.RepositoryProvider; 19 | 20 | /** 21 | * Useful methods for working with projects, repositories, etc. 22 | */ 23 | public class AppraisePluginUtils { 24 | 25 | /** 26 | * Gets the Eclipse project (if any) for the given task repository. 27 | */ 28 | public static IProject getProjectForRepository(TaskRepository repo) { 29 | for (IProject project : ResourcesPlugin.getWorkspace().getRoot().getProjects()) { 30 | if (repo.getUrl().endsWith(project.getName())) { 31 | return project; 32 | } 33 | } 34 | return null; 35 | } 36 | 37 | /** 38 | * Gets the git repository object (if any) associated with the given task repository. 39 | */ 40 | public static Repository getGitRepoForRepository(TaskRepository repo) { 41 | IProject project = getProjectForRepository(repo); 42 | GitProvider provider = (GitProvider) RepositoryProvider.getProvider(project, GitProvider.ID); 43 | if (provider != null) { 44 | return provider.getData().getRepositoryMapping(project).getRepository(); 45 | } 46 | return null; 47 | } 48 | } 49 | 50 | -------------------------------------------------------------------------------- /core/src/com/google/appraise/eclipse/core/AppraiseRepositoryConnector.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Google and others. 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Eclipse Public License v1.0 5 | * which accompanies this distribution, and is available at 6 | * http://www.eclipse.org/legal/epl-v10.html 7 | * 8 | * Contributors: 9 | * Scott McMaster - initial implementation 10 | *******************************************************************************/ 11 | package com.google.appraise.eclipse.core; 12 | 13 | import com.google.appraise.eclipse.core.client.data.ReviewCommentResult; 14 | import com.google.appraise.eclipse.core.client.data.ReviewResult; 15 | import com.google.appraise.eclipse.core.client.git.AppraiseGitReviewClient; 16 | import com.google.appraise.eclipse.core.client.git.GitClientException; 17 | 18 | import org.eclipse.core.runtime.CoreException; 19 | import org.eclipse.core.runtime.IProgressMonitor; 20 | import org.eclipse.core.runtime.IStatus; 21 | import org.eclipse.core.runtime.Status; 22 | import org.eclipse.jgit.diff.DiffEntry; 23 | import org.eclipse.mylyn.tasks.core.AbstractRepositoryConnector; 24 | import org.eclipse.mylyn.tasks.core.IRepositoryQuery; 25 | import org.eclipse.mylyn.tasks.core.ITask; 26 | import org.eclipse.mylyn.tasks.core.TaskRepository; 27 | import org.eclipse.mylyn.tasks.core.data.AbstractTaskDataHandler; 28 | import org.eclipse.mylyn.tasks.core.data.TaskData; 29 | import org.eclipse.mylyn.tasks.core.data.TaskDataCollector; 30 | import org.eclipse.mylyn.tasks.core.data.TaskMapper; 31 | import org.eclipse.mylyn.tasks.core.sync.ISynchronizationSession; 32 | 33 | import java.util.Date; 34 | import java.util.List; 35 | 36 | /** 37 | * The Appraise review repository connector implementation. 38 | */ 39 | public class AppraiseRepositoryConnector extends AbstractRepositoryConnector { 40 | private final AppraiseReviewsTaskDataHandler taskDataHandler; 41 | 42 | public AppraiseRepositoryConnector() { 43 | taskDataHandler = new AppraiseReviewsTaskDataHandler(); 44 | } 45 | 46 | @Override 47 | public boolean canCreateNewTask(TaskRepository repository) { 48 | return true; 49 | } 50 | 51 | @Override 52 | public boolean canCreateTaskFromKey(TaskRepository repository) { 53 | return true; 54 | } 55 | 56 | @Override 57 | public String getConnectorKind() { 58 | return AppraiseConnectorPlugin.CONNECTOR_KIND; 59 | } 60 | 61 | @Override 62 | public String getLabel() { 63 | return "Appraise Reviews"; 64 | } 65 | 66 | @Override 67 | public String getRepositoryUrlFromTaskUrl(String taskUrl) { 68 | return null; 69 | } 70 | 71 | @Override 72 | public TaskData getTaskData(TaskRepository repository, String taskIdOrKey, 73 | IProgressMonitor monitor) throws CoreException { 74 | AppraisePluginReviewClient client; 75 | try { 76 | client = getReviewClient(repository); 77 | } catch (GitClientException e) { 78 | throw new CoreException(new Status(IStatus.ERROR, AppraiseConnectorPlugin.PLUGIN_ID, 79 | "Failed to initialize git client" + taskIdOrKey, e)); 80 | } 81 | 82 | ReviewResult review = client.getReview(taskIdOrKey); 83 | if (review == null) { 84 | throw new CoreException(new Status( 85 | IStatus.ERROR, AppraiseConnectorPlugin.PLUGIN_ID, "Failed to review " + taskIdOrKey)); 86 | } 87 | 88 | List comments; 89 | try { 90 | comments = client.listCommentsForReview(taskIdOrKey); 91 | } catch (Exception e) { 92 | throw new CoreException(new Status(IStatus.ERROR, AppraiseConnectorPlugin.PLUGIN_ID, 93 | "Failed to load review comments for " + taskIdOrKey, e)); 94 | } 95 | 96 | List diffs; 97 | try { 98 | diffs = 99 | new AppraiseGitReviewClient(AppraisePluginUtils.getGitRepoForRepository(repository)) 100 | .getDiff(taskIdOrKey); 101 | } catch (Exception e) { 102 | throw new CoreException(new Status(IStatus.ERROR, AppraiseConnectorPlugin.PLUGIN_ID, 103 | "Failed to load review diffs for " + taskIdOrKey, e)); 104 | } 105 | 106 | boolean isSubmitted = false; 107 | try { 108 | isSubmitted = client.isReviewSubmitted(review); 109 | } catch (Exception e) { 110 | throw new CoreException(new Status(IStatus.ERROR, AppraiseConnectorPlugin.PLUGIN_ID, 111 | "Failed check is-submitted for " + taskIdOrKey, e)); 112 | } 113 | 114 | return taskDataHandler.createFullTaskData(repository, review, comments, diffs, isSubmitted); 115 | } 116 | 117 | @Override 118 | public String getTaskIdFromTaskUrl(String taskUrl) { 119 | return null; 120 | } 121 | 122 | @Override 123 | public String getTaskUrl(String repositoryUrl, String taskIdOrKey) { 124 | return null; 125 | } 126 | 127 | @Override 128 | public boolean hasTaskChanged(TaskRepository taskRepository, ITask task, TaskData taskData) { 129 | Date repositoryDate = getTaskMapping(taskData).getModificationDate(); 130 | Date localDate = task.getModificationDate(); 131 | 132 | if (repositoryDate == null) 133 | return false; 134 | 135 | return !repositoryDate.equals(localDate); 136 | } 137 | 138 | @Override 139 | public IStatus performQuery(TaskRepository repository, IRepositoryQuery query, 140 | TaskDataCollector collector, ISynchronizationSession session, IProgressMonitor monitor) { 141 | AppraisePluginReviewClient client; 142 | 143 | try { 144 | client = getReviewClient(repository); 145 | } catch (GitClientException e) { 146 | AppraiseConnectorPlugin.logError("Failed to initialize git client", e); 147 | return Status.CANCEL_STATUS; 148 | } 149 | 150 | boolean reviewer = 151 | Boolean.parseBoolean(query.getAttribute(AppraiseConnectorPlugin.QUERY_REVIEWER)); 152 | boolean requester = 153 | Boolean.parseBoolean(query.getAttribute(AppraiseConnectorPlugin.QUERY_REQUESTER)); 154 | String reviewCommitPrefix = 155 | query.getAttribute(AppraiseConnectorPlugin.QUERY_REVIEW_COMMIT_PREFIX); 156 | 157 | List reviews = client.listReviews(); 158 | if (reviews == null) { 159 | return new Status(Status.ERROR, AppraiseConnectorPlugin.PLUGIN_ID, 160 | "Error running review list query"); 161 | } 162 | 163 | for (ReviewResult review : reviews) { 164 | TaskData taskData = taskDataHandler.createPartialTaskData(repository, review); 165 | boolean shouldAccept = false; 166 | if (!reviewer && !requester) { 167 | // Accept everything if no filters are set. 168 | shouldAccept = true; 169 | } else if (reviewer && review.isCurrentUserReviewer()) { 170 | shouldAccept = true; 171 | } else if (requester && review.isCurrentUserRequester()) { 172 | shouldAccept = true; 173 | } 174 | 175 | if (reviewCommitPrefix != null && !reviewCommitPrefix.isEmpty()) { 176 | shouldAccept = shouldAccept && review.getHash().startsWith(reviewCommitPrefix); 177 | } 178 | 179 | if (shouldAccept) { 180 | collector.accept(taskData); 181 | } 182 | } 183 | return Status.OK_STATUS; 184 | } 185 | 186 | @Override 187 | public void updateRepositoryConfiguration(TaskRepository taskRepository, IProgressMonitor monitor) 188 | throws CoreException {} 189 | 190 | @Override 191 | public void updateTaskFromTaskData(TaskRepository repository, ITask task, TaskData taskData) { 192 | Date oldModificationDate = task.getModificationDate(); 193 | getTaskMapping(taskData).applyTo(task); 194 | } 195 | 196 | @Override 197 | public TaskMapper getTaskMapping(TaskData taskData) { 198 | return new AppraiseTaskMapper(taskData); 199 | } 200 | 201 | protected AppraisePluginReviewClient getReviewClient(TaskRepository taskRepository) 202 | throws GitClientException { 203 | return new AppraisePluginReviewClient(taskRepository); 204 | } 205 | 206 | @Override 207 | public AbstractTaskDataHandler getTaskDataHandler() { 208 | return taskDataHandler; 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /core/src/com/google/appraise/eclipse/core/AppraiseReviewTaskSchema.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Google and others. 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Eclipse Public License v1.0 5 | * which accompanies this distribution, and is available at 6 | * http://www.eclipse.org/legal/epl-v10.html 7 | * 8 | * Contributors: 9 | * Scott McMaster - initial implementation 10 | *******************************************************************************/ 11 | package com.google.appraise.eclipse.core; 12 | 13 | import org.eclipse.mylyn.tasks.core.data.AbstractTaskSchema; 14 | import org.eclipse.mylyn.tasks.core.data.DefaultTaskSchema; 15 | import org.eclipse.mylyn.tasks.core.data.TaskAttribute; 16 | 17 | /** 18 | * The field definitions for Appraise reviews. 19 | */ 20 | public class AppraiseReviewTaskSchema extends AbstractTaskSchema { 21 | private static final AppraiseReviewTaskSchema instance = new AppraiseReviewTaskSchema(); 22 | 23 | public static AppraiseReviewTaskSchema getDefault() { 24 | return instance; 25 | } 26 | 27 | private final DefaultTaskSchema parent = DefaultTaskSchema.getInstance(); 28 | 29 | public static final String COMMENT_ID_ATTRIBUTE = "appraise.comment.id"; 30 | 31 | public static final String COMMENT_RESOLVED_ATTRIBUTE = "appraise.comment.resolved"; 32 | 33 | public static final String COMMENT_PARENT_ATTRIBUTE = "appraise.comment.parent"; 34 | 35 | public static final String COMMENT_LOCATION_FILE = "appraise.comment.location.file"; 36 | 37 | public static final String COMMENT_LOCATION_LINE = "appraise.comment.location.line"; 38 | 39 | public static final String COMMENT_LOCATION_COMMIT = "appraise.comment.location.commit"; 40 | 41 | public static final String DIFF_TEXT = "appraise.diff.text"; 42 | 43 | public static final String DIFF_NEWPATH = "appraise.diff.newpath"; 44 | 45 | public static final String DIFF_OLDPATH = "appraise.diff.oldpath"; 46 | 47 | public static final String DIFF_TYPE = "appraise.diff.type"; 48 | 49 | public static final String TYPE_DIFF = "appraise.diff"; 50 | 51 | /** 52 | * The prefix for elements of the unified diff for all the commits in the review. 53 | * Not populated in the partial task data. 54 | */ 55 | public static final String PREFIX_DIFF = "com.google.appraise.review.Diff-"; 56 | 57 | public final Field IS_SUBMITTED = 58 | createField("com.google.appraise.review.IsSubmitted", "Submitted", TaskAttribute.TYPE_BOOLEAN, 59 | Flag.READ_ONLY); 60 | 61 | public final Field REVIEW_REF = 62 | createField("com.google.appraise.review.ReviewRef", "Review Ref", TaskAttribute.TYPE_SHORT_TEXT); 63 | 64 | public final Field TARGET_REF = 65 | createField("com.google.appraise.review.TargetRef", "Target Ref", TaskAttribute.TYPE_SHORT_TEXT); 66 | 67 | public final Field DESCRIPTION = inheritFrom(parent.SUMMARY).create(); 68 | 69 | public final Field CREATED = inheritFrom(parent.DATE_CREATION).create(); 70 | 71 | public final Field MODIFIED = inheritFrom(parent.DATE_MODIFICATION).create(); 72 | 73 | public final Field REQUESTER = 74 | createField("com.google.appraise.review.Requester", "Requester", TaskAttribute.TYPE_SHORT_TEXT); 75 | 76 | public final Field REVIEWERS = 77 | createField("com.google.appraise.review.Reviewers", "Reviewers", TaskAttribute.TYPE_SHORT_TEXT); 78 | 79 | public final Field REVIEW_COMMIT = createField("com.google.appraise.review.ReviewCommit", 80 | "Review Commit", TaskAttribute.TYPE_SHORT_TEXT, Flag.READ_ONLY); 81 | 82 | public final Field KIND = inheritFrom(parent.TASK_KIND).create(); 83 | } 84 | -------------------------------------------------------------------------------- /core/src/com/google/appraise/eclipse/core/AppraiseTaskMapper.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Google and others. 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Eclipse Public License v1.0 5 | * which accompanies this distribution, and is available at 6 | * http://www.eclipse.org/legal/epl-v10.html 7 | * 8 | * Contributors: 9 | * Scott McMaster - initial implementation 10 | *******************************************************************************/ 11 | package com.google.appraise.eclipse.core; 12 | 13 | import org.eclipse.mylyn.tasks.core.data.TaskData; 14 | import org.eclipse.mylyn.tasks.core.data.TaskMapper; 15 | 16 | /** 17 | * Custom task mapper for use with Appraise. 18 | */ 19 | public class AppraiseTaskMapper extends TaskMapper { 20 | 21 | public static final String APPRAISE_REVIEW_TASK_KIND = "appraisereview"; 22 | 23 | public AppraiseTaskMapper(TaskData taskData) { 24 | super(taskData); 25 | } 26 | 27 | @Override 28 | public String getTaskKind() { 29 | return APPRAISE_REVIEW_TASK_KIND; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /core/src/com/google/appraise/eclipse/core/client/data/Review.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Google and others. 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Eclipse Public License v1.0 5 | * which accompanies this distribution, and is available at 6 | * http://www.eclipse.org/legal/epl-v10.html 7 | * 8 | * Contributors: 9 | * Scott McMaster - initial implementation 10 | *******************************************************************************/ 11 | package com.google.appraise.eclipse.core.client.data; 12 | 13 | import java.util.Arrays; 14 | 15 | /** 16 | * Appraise review data object. 17 | */ 18 | public class Review { 19 | private String reviewRef; 20 | private String targetRef; 21 | private String requester; 22 | private String[] reviewers; 23 | private String description; 24 | private long timestamp; 25 | 26 | public String getReviewRef() { 27 | return reviewRef; 28 | } 29 | 30 | public void setReviewRef(String reviewRef) { 31 | this.reviewRef = reviewRef; 32 | } 33 | 34 | public String getTargetRef() { 35 | return targetRef; 36 | } 37 | 38 | public void setTargetRef(String targetRef) { 39 | this.targetRef = targetRef; 40 | } 41 | 42 | public String getRequester() { 43 | return requester; 44 | } 45 | 46 | public void setRequester(String requester) { 47 | this.requester = requester; 48 | } 49 | 50 | public String[] getReviewers() { 51 | return reviewers; 52 | } 53 | 54 | public void setReviewers(String[] reviewers) { 55 | this.reviewers = reviewers; 56 | } 57 | 58 | public String getDescription() { 59 | return description; 60 | } 61 | 62 | public void setDescription(String description) { 63 | this.description = description; 64 | } 65 | 66 | public long getTimestamp() { 67 | return timestamp; 68 | } 69 | 70 | public void setTimestamp(long timestamp) { 71 | this.timestamp = timestamp; 72 | } 73 | 74 | /** 75 | * Gets the list of reviews in a comma-delimited format. 76 | */ 77 | public String getReviewersString() { 78 | StringBuilder sb = new StringBuilder(); 79 | if (reviewers != null) { 80 | for (String reviewer : reviewers) { 81 | if (sb.length() > 0) { 82 | sb.append(','); 83 | } 84 | sb.append(reviewer); 85 | } 86 | } 87 | return sb.toString(); 88 | } 89 | 90 | /** 91 | * Sets the list of reviewers from a comma-delimited format. 92 | */ 93 | public void setReviewersString(String reviewersStr) { 94 | if (reviewersStr != null) { 95 | this.reviewers = reviewersStr.split(","); 96 | } else { 97 | this.reviewers = new String[] {}; 98 | } 99 | } 100 | 101 | @Override 102 | public int hashCode() { 103 | final int prime = 31; 104 | int result = 1; 105 | result = prime * result + ((description == null) ? 0 : description.hashCode()); 106 | result = prime * result + ((requester == null) ? 0 : requester.hashCode()); 107 | result = prime * result + ((reviewRef == null) ? 0 : reviewRef.hashCode()); 108 | result = prime * result + Arrays.hashCode(reviewers); 109 | result = prime * result + ((targetRef == null) ? 0 : targetRef.hashCode()); 110 | result = prime * result + (int) (timestamp ^ (timestamp >>> 32)); 111 | return result; 112 | } 113 | 114 | @Override 115 | public boolean equals(Object obj) { 116 | if (this == obj) 117 | return true; 118 | if (obj == null) 119 | return false; 120 | if (getClass() != obj.getClass()) 121 | return false; 122 | Review other = (Review) obj; 123 | if (description == null) { 124 | if (other.description != null) 125 | return false; 126 | } else if (!description.equals(other.description)) 127 | return false; 128 | if (requester == null) { 129 | if (other.requester != null) 130 | return false; 131 | } else if (!requester.equals(other.requester)) 132 | return false; 133 | if (reviewRef == null) { 134 | if (other.reviewRef != null) 135 | return false; 136 | } else if (!reviewRef.equals(other.reviewRef)) 137 | return false; 138 | if (!Arrays.equals(reviewers, other.reviewers)) 139 | return false; 140 | if (targetRef == null) { 141 | if (other.targetRef != null) 142 | return false; 143 | } else if (!targetRef.equals(other.targetRef)) 144 | return false; 145 | if (timestamp != other.timestamp) 146 | return false; 147 | return true; 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /core/src/com/google/appraise/eclipse/core/client/data/ReviewComment.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Google and others. 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Eclipse Public License v1.0 5 | * which accompanies this distribution, and is available at 6 | * http://www.eclipse.org/legal/epl-v10.html 7 | * 8 | * Contributors: 9 | * Scott McMaster - initial implementation 10 | *******************************************************************************/ 11 | package com.google.appraise.eclipse.core.client.data; 12 | 13 | /** 14 | * Appraise review comment data object. 15 | */ 16 | public class ReviewComment { 17 | private String author; 18 | private long timestamp; 19 | private Boolean resolved; 20 | private ReviewCommentLocation location; 21 | private String description; 22 | 23 | /** 24 | * This (if set) is the id of a comment that this comment is in reply to. 25 | * See the javadoc in {@link ReviewCommentResult}. 26 | */ 27 | private String parent; 28 | 29 | public String getDescription() { 30 | return description; 31 | } 32 | 33 | public void setDescription(String description) { 34 | this.description = description; 35 | } 36 | 37 | public String getAuthor() { 38 | return author; 39 | } 40 | 41 | public void setAuthor(String author) { 42 | this.author = author; 43 | } 44 | 45 | public long getTimestamp() { 46 | return timestamp; 47 | } 48 | 49 | public void setTimestamp(long timestamp) { 50 | this.timestamp = timestamp; 51 | } 52 | 53 | public Boolean getResolved() { 54 | return resolved; 55 | } 56 | 57 | public void setResolved(Boolean resolved) { 58 | this.resolved = resolved; 59 | } 60 | 61 | public ReviewCommentLocation getLocation() { 62 | return location; 63 | } 64 | 65 | public void setLocation(ReviewCommentLocation location) { 66 | this.location = location; 67 | } 68 | 69 | public String getParent() { 70 | return parent; 71 | } 72 | 73 | public void setParent(String parent) { 74 | this.parent = parent; 75 | } 76 | } 77 | 78 | -------------------------------------------------------------------------------- /core/src/com/google/appraise/eclipse/core/client/data/ReviewCommentLocation.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Google and others. 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Eclipse Public License v1.0 5 | * which accompanies this distribution, and is available at 6 | * http://www.eclipse.org/legal/epl-v10.html 7 | * 8 | * Contributors: 9 | * Scott McMaster - initial implementation 10 | *******************************************************************************/ 11 | package com.google.appraise.eclipse.core.client.data; 12 | 13 | /** 14 | * Appraise review comment location data object. 15 | */ 16 | public class ReviewCommentLocation { 17 | private String commit; 18 | private String path; 19 | private ReviewCommentLocationRange range; 20 | 21 | public String getCommit() { 22 | return commit; 23 | } 24 | 25 | public void setCommit(String commit) { 26 | this.commit = commit; 27 | } 28 | 29 | public String getPath() { 30 | return path; 31 | } 32 | 33 | public void setPath(String path) { 34 | this.path = path; 35 | } 36 | 37 | public ReviewCommentLocationRange getRange() { 38 | return range; 39 | } 40 | 41 | public void setRange(ReviewCommentLocationRange range) { 42 | this.range = range; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /core/src/com/google/appraise/eclipse/core/client/data/ReviewCommentLocationRange.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Google and others. 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Eclipse Public License v1.0 5 | * which accompanies this distribution, and is available at 6 | * http://www.eclipse.org/legal/epl-v10.html 7 | * 8 | * Contributors: 9 | * Scott McMaster - initial implementation 10 | *******************************************************************************/ 11 | package com.google.appraise.eclipse.core.client.data; 12 | 13 | /** 14 | * Appraise review comment location range data object. 15 | */ 16 | public class ReviewCommentLocationRange { 17 | private int startLine; 18 | 19 | public int getStartLine() { 20 | return startLine; 21 | } 22 | 23 | public void setStartLine(int startLine) { 24 | this.startLine = startLine; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /core/src/com/google/appraise/eclipse/core/client/data/ReviewCommentResult.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Google and others. 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Eclipse Public License v1.0 5 | * which accompanies this distribution, and is available at 6 | * http://www.eclipse.org/legal/epl-v10.html 7 | * 8 | * Contributors: 9 | * Scott McMaster - initial implementation 10 | *******************************************************************************/ 11 | package com.google.appraise.eclipse.core.client.data; 12 | 13 | /** 14 | * Encapsulates a {@link ReviewComment} with additional data (e.g. the SHA-1 15 | * hash of the JSON to use as an id). 16 | */ 17 | public class ReviewCommentResult { 18 | private String id; 19 | private ReviewComment comment; 20 | 21 | public ReviewCommentResult(String id, ReviewComment comment) { 22 | this.id = id; 23 | this.comment = comment; 24 | } 25 | 26 | public String getId() { 27 | return id; 28 | } 29 | 30 | public void setId(String id) { 31 | this.id = id; 32 | } 33 | 34 | public ReviewComment getComment() { 35 | return comment; 36 | } 37 | 38 | public void setComment(ReviewComment comment) { 39 | this.comment = comment; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /core/src/com/google/appraise/eclipse/core/client/data/ReviewResult.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Google and others. 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Eclipse Public License v1.0 5 | * which accompanies this distribution, and is available at 6 | * http://www.eclipse.org/legal/epl-v10.html 7 | * 8 | * Contributors: 9 | * Scott McMaster - initial implementation 10 | *******************************************************************************/ 11 | package com.google.appraise.eclipse.core.client.data; 12 | 13 | import java.util.Arrays; 14 | import java.util.List; 15 | 16 | /** 17 | * Appraise review result, which exposes the hash as an id for the UI to use as a handle. 18 | */ 19 | public class ReviewResult { 20 | private Review review; 21 | private String hash; 22 | private User currentUser; 23 | 24 | public ReviewResult(String hash, User currentUser, Review noteData) { 25 | this.currentUser = currentUser; 26 | this.hash = hash; 27 | this.review = noteData; 28 | } 29 | 30 | public Review getReview() { 31 | return review; 32 | } 33 | 34 | public void setReview(Review review) { 35 | this.review = review; 36 | } 37 | 38 | public String getHash() { 39 | return hash; 40 | } 41 | 42 | public void setHash(String hash) { 43 | this.hash = hash; 44 | } 45 | 46 | public User getCurrentUser() { 47 | return currentUser; 48 | } 49 | 50 | public void setCurrentUser(User currentUser) { 51 | this.currentUser = currentUser; 52 | } 53 | 54 | public boolean isCurrentUserReviewer() { 55 | if (currentUser == null) { 56 | return false; 57 | } 58 | if (review.getReviewers() == null) { 59 | return false; 60 | } 61 | List reviewers = Arrays.asList(review.getReviewers()); 62 | return (reviewers.contains(currentUser.getUserName()) 63 | || reviewers.contains(currentUser.getEmail())); 64 | } 65 | 66 | public boolean isCurrentUserRequester() { 67 | if (currentUser == null) { 68 | return false; 69 | } 70 | return (currentUser.getUserName().equals(review.getRequester()) 71 | || currentUser.getEmail().equals(review.getRequester())); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /core/src/com/google/appraise/eclipse/core/client/data/User.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Google and others. 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Eclipse Public License v1.0 5 | * which accompanies this distribution, and is available at 6 | * http://www.eclipse.org/legal/epl-v10.html 7 | * 8 | * Contributors: 9 | * Scott McMaster - initial implementation 10 | *******************************************************************************/ 11 | package com.google.appraise.eclipse.core.client.data; 12 | 13 | /** 14 | * Represents an Appraise user (which currently is basically a Git user). 15 | */ 16 | public class User { 17 | 18 | private String userName; 19 | private String email; 20 | 21 | public User(String userName, String email) { 22 | this.userName = userName; 23 | this.email = email; 24 | } 25 | 26 | public String getUserName() { 27 | return userName; 28 | } 29 | 30 | public void setUserName(String userName) { 31 | this.userName = userName; 32 | } 33 | 34 | public String getEmail() { 35 | return email; 36 | } 37 | 38 | public void setEmail(String email) { 39 | this.email = email; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /core/src/com/google/appraise/eclipse/core/client/git/GitClientException.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Google and others. 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Eclipse Public License v1.0 5 | * which accompanies this distribution, and is available at 6 | * http://www.eclipse.org/legal/epl-v10.html 7 | * 8 | * Contributors: 9 | * Scott McMaster - initial implementation 10 | *******************************************************************************/ 11 | package com.google.appraise.eclipse.core.client.git; 12 | 13 | /** 14 | * Custom exception thrown by the git client for Appraise. 15 | */ 16 | public class GitClientException extends Exception { 17 | 18 | private static final long serialVersionUID = 4935975503414735796L; 19 | 20 | public GitClientException() { 21 | super(); 22 | } 23 | 24 | public GitClientException(String message, Throwable cause) { 25 | super(message, cause); 26 | } 27 | 28 | public GitClientException(String message) { 29 | super(message); 30 | } 31 | 32 | public GitClientException(Throwable cause) { 33 | super(cause); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /core/src/com/google/appraise/eclipse/core/client/git/GitNoteWriter.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Google and others. 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Eclipse Public License v1.0 5 | * which accompanies this distribution, and is available at 6 | * http://www.eclipse.org/legal/epl-v10.html 7 | * 8 | * Contributors: 9 | * Scott McMaster - initial implementation 10 | *******************************************************************************/ 11 | package com.google.appraise.eclipse.core.client.git; 12 | 13 | import com.google.gson.Gson; 14 | 15 | import org.eclipse.jgit.errors.CorruptObjectException; 16 | import org.eclipse.jgit.errors.IncorrectObjectTypeException; 17 | import org.eclipse.jgit.errors.MissingObjectException; 18 | import org.eclipse.jgit.lib.CommitBuilder; 19 | import org.eclipse.jgit.lib.Constants; 20 | import org.eclipse.jgit.lib.ObjectId; 21 | import org.eclipse.jgit.lib.ObjectInserter; 22 | import org.eclipse.jgit.lib.ObjectReader; 23 | import org.eclipse.jgit.lib.PersonIdent; 24 | import org.eclipse.jgit.lib.Ref; 25 | import org.eclipse.jgit.lib.RefUpdate; 26 | import org.eclipse.jgit.lib.RefUpdate.Result; 27 | import org.eclipse.jgit.lib.Repository; 28 | import org.eclipse.jgit.notes.DefaultNoteMerger; 29 | import org.eclipse.jgit.notes.Note; 30 | import org.eclipse.jgit.notes.NoteMap; 31 | import org.eclipse.jgit.notes.NoteMapMerger; 32 | import org.eclipse.jgit.notes.NoteMerger; 33 | import org.eclipse.jgit.revwalk.RevCommit; 34 | import org.eclipse.jgit.revwalk.RevWalk; 35 | 36 | import java.io.Closeable; 37 | import java.io.IOException; 38 | import java.util.List; 39 | import java.util.logging.Level; 40 | import java.util.logging.Logger; 41 | 42 | /** 43 | * Writes git notes in the format that Appraise expects. 44 | * This is based off an old version of Gerrit's CreateCodeReviewNotes 45 | * (https://code.google.com/p/gerrit/source/browse/gerrit-server/src/main/java/com/google/gerrit/server/git/CreateCodeReviewNotes.java), 46 | * which is about the only non-trivial git-notes Jgit example on the web. 47 | * 48 | * @param The type of note to write, which will be serialized as a JSON 49 | * string and appended with a newline. 50 | */ 51 | public class GitNoteWriter implements Closeable { 52 | private static final Logger logger = Logger.getLogger(GitNoteWriter.class.getName()); 53 | 54 | /** 55 | * The ref where the notes are to be written. 56 | */ 57 | private final String ref; 58 | 59 | // Some Jgit objects. 60 | private final Repository repo; 61 | private final RevWalk revWalk; 62 | private final ObjectInserter inserter; 63 | private final ObjectReader reader; 64 | 65 | // The base commit that we need to merge into. 66 | private RevCommit baseCommit; 67 | private NoteMap base; 68 | 69 | // The thing we need to merge. 70 | private RevCommit oursCommit; 71 | private NoteMap ours; 72 | 73 | private List noteRecords; 74 | 75 | /** 76 | * Who is writing the notes out, which is assumed to be the same person making 77 | * the reviews/comments in the first place. 78 | */ 79 | private PersonIdent author; 80 | 81 | /** 82 | * The commit that we want to attach to. 83 | */ 84 | private final RevCommit reviewCommit; 85 | 86 | /** 87 | * Creates a writer to write comments to a given review. 88 | */ 89 | public static GitNoteWriter createNoteWriter( 90 | String reviewCommitHash, final Repository db, PersonIdent author, String ref) { 91 | return new GitNoteWriter(reviewCommitHash, db, ref, author); 92 | } 93 | 94 | /** 95 | * Private ctor. Use the static factory methods. 96 | */ 97 | private GitNoteWriter(String reviewHash, final Repository repo, String ref, PersonIdent author) { 98 | this.ref = ref; 99 | this.repo = repo; 100 | this.author = author; 101 | 102 | revWalk = new RevWalk(repo); 103 | inserter = repo.newObjectInserter(); 104 | reader = repo.newObjectReader(); 105 | 106 | try { 107 | ObjectId reviewRefObjId = repo.resolve(reviewHash); 108 | this.reviewCommit = revWalk.parseCommit(reviewRefObjId); 109 | } catch (Exception e) { 110 | logger.log(Level.SEVERE, "Failed to init note writer for commit " + reviewHash, e); 111 | throw new RuntimeException(e); 112 | } 113 | } 114 | 115 | /** 116 | * Creates the given notes in the pre-configured review. 117 | */ 118 | public void create(String message, List noteRecords) { 119 | try { 120 | this.noteRecords = noteRecords; 121 | loadBase(); 122 | applyNotes(message); 123 | updateRef(); 124 | } catch (Exception e) { 125 | logger.log(Level.SEVERE, 126 | "Failed to write notes for commit " + this.reviewCommit.getId(), e); 127 | } 128 | } 129 | 130 | private void loadBase() throws IOException { 131 | Ref notesBranch = repo.getRef(ref); 132 | if (notesBranch != null) { 133 | baseCommit = revWalk.parseCommit(notesBranch.getObjectId()); 134 | base = NoteMap.read(revWalk.getObjectReader(), baseCommit); 135 | } 136 | if (baseCommit != null) { 137 | ours = NoteMap.read(repo.newObjectReader(), baseCommit); 138 | } else { 139 | ours = NoteMap.newEmptyMap(); 140 | } 141 | } 142 | 143 | private void applyNotes(String message) throws IOException { 144 | for (T c : noteRecords) { 145 | add(c); 146 | } 147 | commit(message.toString()); 148 | } 149 | 150 | private void commit(String message) throws IOException { 151 | if (baseCommit != null) { 152 | oursCommit = createCommit(ours, author, message, baseCommit); 153 | } else { 154 | oursCommit = createCommit(ours, author, message); 155 | } 156 | } 157 | 158 | private void add(T noteRecord) 159 | throws MissingObjectException, IncorrectObjectTypeException, IOException, RuntimeException { 160 | ObjectId noteContent = createNoteContent(noteRecord); 161 | if (ours.contains(reviewCommit)) { 162 | // merge the existing and the new note as if they are both new 163 | // means: base == null 164 | // there is not really a common ancestry for these two note revisions 165 | // use the same NoteMerger that is used from the NoteMapMerger 166 | NoteMerger noteMerger = new DefaultNoteMerger(); 167 | Note newNote = new Note(reviewCommit, noteContent); 168 | noteContent = 169 | noteMerger.merge(null, newNote, ours.getNote(reviewCommit), reader, inserter).getData(); 170 | } 171 | ours.set(reviewCommit, noteContent); 172 | } 173 | 174 | private ObjectId createNoteContent(T noteRecord) throws RuntimeException { 175 | try { 176 | return inserter.insert( 177 | Constants.OBJ_BLOB, (new Gson().toJson(noteRecord) + '\n').getBytes("UTF-8")); 178 | } catch (Exception e) { 179 | logger.log(Level.SEVERE, 180 | "Failed create note content for commit " + this.reviewCommit.getId(), e); 181 | throw new RuntimeException(e); 182 | } 183 | } 184 | 185 | private void updateRef() throws IOException, InterruptedException, RuntimeException, 186 | MissingObjectException, IncorrectObjectTypeException, 187 | CorruptObjectException { 188 | if (baseCommit != null && oursCommit.getTree().equals(baseCommit.getTree())) { 189 | // If the trees are identical, there is no change in the notes. 190 | // Avoid saving this commit as it has no new information. 191 | return; 192 | } 193 | 194 | int remainingLockFailureCalls = JgitUtils.MAX_LOCK_FAILURE_CALLS; 195 | RefUpdate refUpdate = JgitUtils.updateRef(repo, oursCommit, baseCommit, ref); 196 | 197 | for (;;) { 198 | Result result = refUpdate.update(); 199 | 200 | if (result == Result.LOCK_FAILURE) { 201 | if (--remainingLockFailureCalls > 0) { 202 | Thread.sleep(JgitUtils.SLEEP_ON_LOCK_FAILURE_MS); 203 | } else { 204 | throw new RuntimeException("Failed to lock the ref: " + ref); 205 | } 206 | 207 | } else if (result == Result.REJECTED) { 208 | RevCommit theirsCommit = revWalk.parseCommit(refUpdate.getOldObjectId()); 209 | NoteMap theirs = NoteMap.read(revWalk.getObjectReader(), theirsCommit); 210 | NoteMapMerger merger = new NoteMapMerger(repo); 211 | NoteMap merged = merger.merge(base, ours, theirs); 212 | RevCommit mergeCommit = 213 | createCommit(merged, author, "Merged note records\n", theirsCommit, oursCommit); 214 | refUpdate = JgitUtils.updateRef(repo, mergeCommit, theirsCommit, ref); 215 | remainingLockFailureCalls = JgitUtils.MAX_LOCK_FAILURE_CALLS; 216 | 217 | } else if (result == Result.IO_FAILURE) { 218 | throw new RuntimeException("Couldn't create notes because of IO_FAILURE"); 219 | } else { 220 | break; 221 | } 222 | } 223 | } 224 | 225 | @Override 226 | public void close() { 227 | reader.close(); 228 | inserter.close(); 229 | revWalk.close(); 230 | } 231 | 232 | private RevCommit createCommit( 233 | NoteMap map, PersonIdent author, String message, RevCommit... parents) throws IOException { 234 | CommitBuilder b = new CommitBuilder(); 235 | b.setTreeId(map.writeTree(inserter)); 236 | b.setAuthor(author); 237 | b.setCommitter(author); 238 | if (parents.length > 0) { 239 | b.setParentIds(parents); 240 | } 241 | b.setMessage(message); 242 | ObjectId commitId = inserter.insert(b); 243 | inserter.flush(); 244 | return revWalk.parseCommit(commitId); 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /core/src/com/google/appraise/eclipse/core/client/git/JgitUtils.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Google and others. 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Eclipse Public License v1.0 5 | * which accompanies this distribution, and is available at 6 | * http://www.eclipse.org/legal/epl-v10.html 7 | * 8 | * Contributors: 9 | * Scott McMaster - initial implementation 10 | *******************************************************************************/ 11 | package com.google.appraise.eclipse.core.client.git; 12 | 13 | import org.eclipse.jgit.lib.ObjectId; 14 | import org.eclipse.jgit.lib.RefUpdate; 15 | import org.eclipse.jgit.lib.Repository; 16 | 17 | import java.io.IOException; 18 | 19 | /** 20 | * Common utility routines for working with Jgit. 21 | */ 22 | public class JgitUtils { 23 | 24 | // Some retry stuff. 25 | public static final int MAX_LOCK_FAILURE_CALLS = 10; 26 | public static final int SLEEP_ON_LOCK_FAILURE_MS = 25; 27 | 28 | /** 29 | * Updates the given ref to the new commit. 30 | */ 31 | public static RefUpdate updateRef(Repository repo, ObjectId newObjectId, 32 | ObjectId expectedOldObjectId, String refName) throws IOException { 33 | RefUpdate refUpdate = repo.updateRef(refName); 34 | refUpdate.setNewObjectId(newObjectId); 35 | if (expectedOldObjectId == null) { 36 | refUpdate.setExpectedOldObjectId(ObjectId.zeroId()); 37 | } else { 38 | refUpdate.setExpectedOldObjectId(expectedOldObjectId); 39 | } 40 | return refUpdate; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /doc/screenshots/New-Line-Comment-Menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/git-appraise-eclipse/575f41a78c0149d4d72d116578c8f7ad16ff6c7f/doc/screenshots/New-Line-Comment-Menu.png -------------------------------------------------------------------------------- /doc/screenshots/Review-Display-With-Comments.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/git-appraise-eclipse/575f41a78c0149d4d72d116578c8f7ad16ff6c7f/doc/screenshots/Review-Display-With-Comments.png -------------------------------------------------------------------------------- /doc/screenshots/Review-Display.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/git-appraise-eclipse/575f41a78c0149d4d72d116578c8f7ad16ff6c7f/doc/screenshots/Review-Display.png -------------------------------------------------------------------------------- /feature/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /feature/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | feature 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.pde.FeatureBuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.pde.FeatureNature 16 | 17 | 18 | -------------------------------------------------------------------------------- /feature/build.properties: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Copyright (c) 2015 Google and others. 3 | # All rights reserved. This program and the accompanying materials 4 | # are made available under the terms of the Eclipse Public License v1.0 5 | # which accompanies this distribution, and is available at 6 | # http://www.eclipse.org/legal/epl-v10.html 7 | # 8 | # Contributors: 9 | # Scott D McMaster - initial implementation 10 | ############################################################################### 11 | bin.includes = feature.xml 12 | -------------------------------------------------------------------------------- /feature/feature.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | Google Appraise Plugin for Eclipse 9 | 10 | 11 | 12 | (C) 2015 Google, Inc. and others 13 | 14 | 15 | 16 | Eclipse Public License - v 1.0 17 | 18 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 19 | 20 | 1. DEFINITIONS 21 | 22 | "Contribution" means: 23 | 24 | a) in the case of the initial Contributor, the initial code and documentation distributed under this Agreement, and 25 | b) in the case of each subsequent Contributor: 26 | i) changes to the Program, and 27 | ii) additions to the Program; 28 | where such changes and/or additions to the Program originate from and are distributed by that particular Contributor. A Contribution 'originates' from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include additions to the Program which: (i) are separate modules of software distributed in conjunction with the Program under their own license agreement, and (ii) are not derivative works of the Program. 29 | "Contributor" means any person or entity that distributes the Program. 30 | 31 | "Licensed Patents" mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. 32 | 33 | "Program" means the Contributions distributed in accordance with this Agreement. 34 | 35 | "Recipient" means anyone who receives the Program under this Agreement, including all Contributors. 36 | 37 | 2. GRANT OF RIGHTS 38 | 39 | a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, distribute and sublicense the Contribution of such Contributor, if any, and such derivative works, in source code and object code form. 40 | b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in source code and object code form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder. 41 | c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program. 42 | d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. 43 | 3. REQUIREMENTS 44 | 45 | A Contributor may choose to distribute the Program in object code form under its own license agreement, provided that: 46 | 47 | a) it complies with the terms and conditions of this Agreement; and 48 | b) its license agreement: 49 | i) effectively disclaims on behalf of all Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose; 50 | ii) effectively excludes on behalf of all Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits; 51 | iii) states that any provisions which differ from this Agreement are offered by that Contributor alone and not by any other party; and 52 | iv) states that source code for the Program is available from such Contributor, and informs licensees how to obtain it in a reasonable manner on or through a medium customarily used for software exchange. 53 | When the Program is made available in source code form: 54 | 55 | a) it must be made available under this Agreement; and 56 | b) a copy of this Agreement must be included with each copy of the Program. 57 | Contributors may not remove or alter any copyright notices contained within the Program. 58 | 59 | Each Contributor must identify itself as the originator of its Contribution, if any, in a manner that reasonably allows subsequent Recipients to identify the originator of the Contribution. 60 | 61 | 4. COMMERCIAL DISTRIBUTION 62 | 63 | Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor ("Commercial Contributor") hereby agrees to defend and indemnify every other Contributor ("Indemnified Contributor") against any losses, damages and costs (collectively "Losses") arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense. 64 | 65 | For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages. 66 | 67 | 5. NO WARRANTY 68 | 69 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED 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. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement , including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations. 70 | 71 | 6. DISCLAIMER OF LIABILITY 72 | 73 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 74 | 75 | 7. GENERAL 76 | 77 | If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. 78 | 79 | If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed. 80 | 81 | All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. 82 | 83 | Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to distribute the Program (including its Contributions) under the new version. Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved. 84 | 85 | This Agreement is governed by the laws of the State of New York and the intellectual property laws of the United States of America. No party to this Agreement will bring a legal action under this Agreement more than one year after the cause of action arose. Each party waives its rights to a jury trial in any resulting litigation. 86 | 87 | 88 | 94 | 95 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /feature/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.google.appraise 7 | eclipse-plugin 8 | 0.0.1-SNAPSHOT 9 | 10 | com.google.appraise 11 | com.google.appraise.eclipse.feature 12 | 1.0.0-SNAPSHOT 13 | eclipse-feature 14 | 15 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | com.google.appraise 6 | eclipse-plugin 7 | 0.0.1-SNAPSHOT 8 | pom 9 | 10 | core 11 | feature 12 | site 13 | ui 14 | 15 | 16 | 17 | eclipse-indigo 18 | p2 19 | https://download.eclipse.org/releases/mars 20 | 21 | 22 | 23 | 24 | 25 | org.eclipse.tycho 26 | tycho-maven-plugin 27 | 0.24.0 28 | true 29 | 30 | 31 | org.eclipse.tycho 32 | tycho-versions-plugin 33 | 0.24.0 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /site/.gitignore: -------------------------------------------------------------------------------- 1 | target/repository/* 2 | target/site/* 3 | target/*.properties 4 | target/*.xml 5 | *.jar 6 | *.zip 7 | -------------------------------------------------------------------------------- /site/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | site 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.pde.UpdateSiteBuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.pde.UpdateSiteNature 16 | 17 | 18 | -------------------------------------------------------------------------------- /site/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | site 4 | 5 | 6 | 55 | 56 | 57 | 58 |
59 | 60 | 61 | -------------------------------------------------------------------------------- /site/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.google.appraise 7 | eclipse-plugin 8 | 0.0.1-SNAPSHOT 9 | 10 | com.google.appraise 11 | site 12 | 0.0.1-SNAPSHOT 13 | eclipse-update-site 14 | 15 | 16 | 17 | org.eclipse.tycho 18 | tycho-packaging-plugin 19 | 20 | true 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /site/site.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Google Appraise git-notes-based code reviews. 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /site/web/site.css: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /site/web/site.xsl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | site 9 | 10 | 11 | 12 |

site

13 |

14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | dark-row 34 | 35 | 36 | light-row 37 | 38 | 39 | 54 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | dark-row 106 | 107 | 108 | light-row 109 | 110 | 111 | 126 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | dark-row 161 | 162 | 163 | light-row 164 | 165 | 166 | 181 | 205 | 206 | 207 | 208 | 209 |
21 | 22 | 24 | 25 |
40 | 41 | 42 | 43 |
44 |
45 | ( - ) 46 |
47 |
48 | 49 | - 50 | 51 |
52 |
53 |
55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 |
Operating Systems:
Windows Systems:
Languages:
Architecture:
77 |


86 | Uncategorized 87 |
112 | 113 | 114 | 115 |
116 |
117 | ( - ) 118 |
119 |
120 | 121 | - 122 | 123 |
124 |

125 |
127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 |
Operating Systems:
Windows Systems:
Languages:
Architecture:
149 |
167 | 168 | 169 | 170 |
171 |
172 | ( - ) 173 |
174 |
175 | 176 | - 177 | 178 |
179 |

180 |
182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 |
Operating Systems:
Windows Systems:
Languages:
Architecture:
204 |
210 | 211 | 212 |
213 |
214 |
215 | -------------------------------------------------------------------------------- /ui/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ui/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /ui/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | com.google.appraise.eclipse.ui 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.pde.ManifestBuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.pde.SchemaBuilder 20 | 21 | 22 | 23 | 24 | 25 | org.eclipse.pde.PluginNature 26 | org.eclipse.jdt.core.javanature 27 | 28 | 29 | -------------------------------------------------------------------------------- /ui/.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 3 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 4 | org.eclipse.jdt.core.compiler.compliance=1.7 5 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error 6 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error 7 | org.eclipse.jdt.core.compiler.source=1.7 8 | -------------------------------------------------------------------------------- /ui/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Bundle-ManifestVersion: 2 3 | Bundle-Name: Appraise Connector Ui 4 | Bundle-SymbolicName: com.google.appraise.eclipse.ui;singleton:=true 5 | Bundle-Version: 1.0.0.qualifier 6 | Bundle-Activator: com.google.appraise.eclipse.ui.AppraiseUiPlugin 7 | Bundle-Vendor: Google 8 | Require-Bundle: org.eclipse.ui, 9 | org.eclipse.core.runtime, 10 | org.eclipse.ui.forms;bundle-version="3.6.100", 11 | com.google.appraise.eclipse.core;bundle-version="1.0.0", 12 | org.eclipse.core.resources;bundle-version="3.9.1", 13 | org.eclipse.jgit, 14 | org.eclipse.ui.ide;bundle-version="3.10.3", 15 | org.eclipse.ui.workbench.texteditor;bundle-version="3.9.0", 16 | org.eclipse.jface.text;bundle-version="3.10.0", 17 | org.eclipse.core.expressions;bundle-version="3.4.600", 18 | org.eclipse.mylyn.tasks.core;bundle-version="3.9.0", 19 | org.eclipse.mylyn.tasks.ui;bundle-version="3.9.0", 20 | org.eclipse.mylyn.commons.core;bundle-version="3.9.0", 21 | org.eclipse.mylyn.commons.ui;bundle-version="3.9.0", 22 | org.eclipse.mylyn.commons.workbench;bundle-version="3.9.0", 23 | org.eclipse.ui.editors;bundle-version="3.9.0", 24 | ch.qos.logback.classic;bundle-version="1.0.7", 25 | org.eclipse.egit.ui;bundle-version="4.0.0", 26 | org.eclipse.core.filesystem;bundle-version="1.5.0" 27 | Bundle-RequiredExecutionEnvironment: JavaSE-1.7 28 | Bundle-ActivationPolicy: lazy 29 | -------------------------------------------------------------------------------- /ui/bin/.gitignore: -------------------------------------------------------------------------------- 1 | /com/ 2 | -------------------------------------------------------------------------------- /ui/build.properties: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Copyright (c) 2015 Google and others. 3 | # All rights reserved. This program and the accompanying materials 4 | # are made available under the terms of the Eclipse Public License v1.0 5 | # which accompanies this distribution, and is available at 6 | # http://www.eclipse.org/legal/epl-v10.html 7 | # 8 | # Contributors: 9 | # Scott McMaster - initial implementation 10 | ############################################################################### 11 | source.. = src/ 12 | output.. = bin/ 13 | bin.includes = META-INF/,\ 14 | .,\ 15 | plugin.xml 16 | -------------------------------------------------------------------------------- /ui/icons/appraise-icon.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/git-appraise-eclipse/575f41a78c0149d4d72d116578c8f7ad16ff6c7f/ui/icons/appraise-icon.gif -------------------------------------------------------------------------------- /ui/icons/greencheck.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/git-appraise-eclipse/575f41a78c0149d4d72d116578c8f7ad16ff6c7f/ui/icons/greencheck.png -------------------------------------------------------------------------------- /ui/icons/overlay-appraise.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/git-appraise-eclipse/575f41a78c0149d4d72d116578c8f7ad16ff6c7f/ui/icons/overlay-appraise.gif -------------------------------------------------------------------------------- /ui/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 9 | 10 | 15 | 16 | 17 | 19 | 22 | 23 | 24 | 26 | 29 | 30 | 31 | 32 | 34 | 39 | 40 | 41 | 42 | 44 | 45 | 49 | 50 | 51 | 55 | 56 | 57 | 61 | 62 | 63 | 66 | 68 | 70 | 72 | 74 | 76 | 78 | 80 | 82 | 84 | 85 | 86 | 87 | 90 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 106 | 109 | 110 | 111 | 114 | 117 | 118 | 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /ui/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.google.appraise 8 | eclipse-plugin 9 | 0.0.1-SNAPSHOT 10 | 11 | com.google.appraise 12 | com.google.appraise.eclipse.ui 13 | 1.0.0-SNAPSHOT 14 | eclipse-plugin 15 | 16 | 17 | 18 | org.apache.maven.plugins 19 | maven-compiler-plugin 20 | 3.3 21 | 22 | 1.7 23 | 1.7 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /ui/src/com/google/appraise/eclipse/ui/AppraiseConnectorUi.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Google and others. 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Eclipse Public License v1.0 5 | * which accompanies this distribution, and is available at 6 | * http://www.eclipse.org/legal/epl-v10.html 7 | * 8 | * Contributors: 9 | * Scott McMaster - initial implementation 10 | *******************************************************************************/ 11 | package com.google.appraise.eclipse.ui; 12 | 13 | import com.google.appraise.eclipse.core.AppraiseConnectorPlugin; 14 | import com.google.appraise.eclipse.ui.wizard.AppraiseRepositorySettingsPage; 15 | 16 | import org.eclipse.jface.wizard.IWizard; 17 | import org.eclipse.mylyn.tasks.core.IRepositoryQuery; 18 | import org.eclipse.mylyn.tasks.core.ITaskMapping; 19 | import org.eclipse.mylyn.tasks.core.TaskRepository; 20 | import org.eclipse.mylyn.tasks.ui.AbstractRepositoryConnectorUi; 21 | import org.eclipse.mylyn.tasks.ui.wizards.ITaskRepositoryPage; 22 | import org.eclipse.mylyn.tasks.ui.wizards.NewTaskWizard; 23 | import org.eclipse.mylyn.tasks.ui.wizards.RepositoryQueryWizard; 24 | 25 | /** 26 | * The UI extensions for the Appraise plugin. 27 | */ 28 | public class AppraiseConnectorUi extends AbstractRepositoryConnectorUi { 29 | 30 | @Override 31 | public String getConnectorKind() { 32 | return AppraiseConnectorPlugin.CONNECTOR_KIND; 33 | } 34 | 35 | @Override 36 | public ITaskRepositoryPage getSettingsPage(TaskRepository repository) { 37 | return new AppraiseRepositorySettingsPage(repository); 38 | } 39 | 40 | @Override 41 | public IWizard getQueryWizard(TaskRepository repository, IRepositoryQuery query) { 42 | RepositoryQueryWizard wizard = new RepositoryQueryWizard(repository); 43 | wizard.addPage(new AppraiseReviewsQueryPage(repository, query)); 44 | return wizard; 45 | } 46 | 47 | @Override 48 | public IWizard getNewTaskWizard(TaskRepository repository, ITaskMapping selection) { 49 | return new NewTaskWizard(repository, selection); 50 | } 51 | 52 | @Override 53 | public boolean hasSearchPage() { 54 | return true; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /ui/src/com/google/appraise/eclipse/ui/AppraiseReviewMarkerView.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Google and others. 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Eclipse Public License v1.0 5 | * which accompanies this distribution, and is available at 6 | * http://www.eclipse.org/legal/epl-v10.html 7 | * 8 | * Contributors: 9 | * Scott McMaster - initial implementation 10 | *******************************************************************************/ 11 | package com.google.appraise.eclipse.ui; 12 | 13 | import org.eclipse.ui.views.markers.MarkerSupportView; 14 | 15 | /** 16 | * Custom marker view for Appraise-style review tasks. 17 | */ 18 | public class AppraiseReviewMarkerView extends MarkerSupportView { 19 | public AppraiseReviewMarkerView() { 20 | super("com.google.appraise.eclipse.ui.reviewMarkerGenerator"); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ui/src/com/google/appraise/eclipse/ui/AppraiseReviewsQueryPage.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Google and others. 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Eclipse Public License v1.0 5 | * which accompanies this distribution, and is available at 6 | * http://www.eclipse.org/legal/epl-v10.html 7 | * 8 | * Contributors: 9 | * Scott McMaster - initial implementation 10 | *******************************************************************************/ 11 | package com.google.appraise.eclipse.ui; 12 | 13 | import com.google.appraise.eclipse.core.AppraiseConnectorPlugin; 14 | 15 | import org.eclipse.mylyn.commons.workbench.forms.SectionComposite; 16 | import org.eclipse.mylyn.tasks.core.IRepositoryQuery; 17 | import org.eclipse.mylyn.tasks.core.TaskRepository; 18 | import org.eclipse.mylyn.tasks.ui.wizards.AbstractRepositoryQueryPage2; 19 | import org.eclipse.swt.SWT; 20 | import org.eclipse.swt.layout.GridLayout; 21 | import org.eclipse.swt.widgets.Button; 22 | import org.eclipse.swt.widgets.Composite; 23 | import org.eclipse.swt.widgets.Label; 24 | import org.eclipse.swt.widgets.Text; 25 | 26 | /** 27 | * Custom Appraise review query page. 28 | */ 29 | public class AppraiseReviewsQueryPage extends AbstractRepositoryQueryPage2 { 30 | private Button requesterCheckbox; 31 | private Button reviewerCheckbox; 32 | private Text reviewCommitPrefixText; 33 | 34 | public AppraiseReviewsQueryPage(TaskRepository repository, IRepositoryQuery query) { 35 | super("reviews", repository, query); 36 | setTitle("Appraise Review Search"); 37 | setDescription("Specify search parameters."); 38 | } 39 | 40 | @Override 41 | protected void createPageContent(SectionComposite parent) { 42 | Composite composite = parent.getContent(); 43 | composite.setLayout(new GridLayout(2, false)); 44 | requesterCheckbox = new Button(composite, SWT.CHECK); 45 | requesterCheckbox.setText("Requester"); 46 | reviewerCheckbox = new Button(composite, SWT.CHECK); 47 | reviewerCheckbox.setText("Reviewer"); 48 | 49 | Label reviewCommitPrefixLabel = new Label(composite, SWT.NONE); 50 | reviewCommitPrefixLabel.setText("Review Commit Hash (prefix):"); 51 | reviewCommitPrefixText = new Text(composite, SWT.SINGLE | SWT.LEFT | SWT.BORDER); 52 | composite.pack(); 53 | } 54 | 55 | @Override 56 | protected boolean hasRepositoryConfiguration() { 57 | return true; 58 | } 59 | 60 | @Override 61 | protected boolean restoreState(IRepositoryQuery query) { 62 | String requester = query.getAttribute(AppraiseConnectorPlugin.QUERY_REQUESTER); 63 | requesterCheckbox.setSelection(Boolean.parseBoolean(requester)); 64 | 65 | String reviewer = query.getAttribute(AppraiseConnectorPlugin.QUERY_REVIEWER); 66 | reviewerCheckbox.setSelection(Boolean.parseBoolean(reviewer)); 67 | 68 | String reviewCommitPrefix = query.getAttribute(AppraiseConnectorPlugin.QUERY_REVIEW_COMMIT_PREFIX); 69 | reviewCommitPrefixText.setText(reviewCommitPrefix); 70 | 71 | return true; 72 | } 73 | 74 | @Override 75 | public void applyTo(IRepositoryQuery query) { 76 | if (getQueryTitle() != null) { 77 | query.setSummary(getQueryTitle()); 78 | } 79 | query.setAttribute( 80 | AppraiseConnectorPlugin.QUERY_REQUESTER, Boolean.toString(requesterCheckbox.getSelection())); 81 | query.setAttribute( 82 | AppraiseConnectorPlugin.QUERY_REVIEWER, Boolean.toString(reviewerCheckbox.getSelection())); 83 | query.setAttribute(AppraiseConnectorPlugin.QUERY_REVIEW_COMMIT_PREFIX, reviewCommitPrefixText.getText()); 84 | } 85 | 86 | @Override 87 | protected void doRefreshControls() { 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /ui/src/com/google/appraise/eclipse/ui/AppraiseUiPlugin.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Google and others. 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Eclipse Public License v1.0 5 | * which accompanies this distribution, and is available at 6 | * http://www.eclipse.org/legal/epl-v10.html 7 | * 8 | * Contributors: 9 | * Scott McMaster - initial implementation 10 | *******************************************************************************/ 11 | package com.google.appraise.eclipse.ui; 12 | 13 | import com.google.appraise.eclipse.core.AppraiseConnectorPlugin; 14 | import com.google.appraise.eclipse.core.AppraisePluginReviewClient; 15 | import com.google.appraise.eclipse.core.AppraisePluginUtils; 16 | import com.google.appraise.eclipse.core.AppraiseTaskMapper; 17 | import com.google.appraise.eclipse.core.client.data.ReviewComment; 18 | import com.google.appraise.eclipse.core.client.git.GitClientException; 19 | 20 | import org.eclipse.core.filesystem.EFS; 21 | import org.eclipse.core.filesystem.IFileStore; 22 | import org.eclipse.core.resources.IFile; 23 | import org.eclipse.core.resources.IWorkspaceRoot; 24 | import org.eclipse.core.resources.ResourcesPlugin; 25 | import org.eclipse.core.runtime.IStatus; 26 | import org.eclipse.core.runtime.Path; 27 | import org.eclipse.core.runtime.Status; 28 | import org.eclipse.jface.util.OpenStrategy; 29 | import org.eclipse.jgit.lib.Repository; 30 | import org.eclipse.mylyn.tasks.core.ITask; 31 | import org.eclipse.mylyn.tasks.core.TaskRepository; 32 | import org.eclipse.mylyn.tasks.ui.TasksUi; 33 | import org.eclipse.ui.IWorkbenchPage; 34 | import org.eclipse.ui.IWorkbenchWindow; 35 | import org.eclipse.ui.PartInitException; 36 | import org.eclipse.ui.PlatformUI; 37 | import org.eclipse.ui.editors.text.EditorsUI; 38 | import org.eclipse.ui.ide.FileStoreEditorInput; 39 | import org.eclipse.ui.ide.IDE; 40 | import org.eclipse.ui.plugin.AbstractUIPlugin; 41 | import org.osgi.framework.BundleContext; 42 | 43 | import java.io.File; 44 | import java.io.IOException; 45 | 46 | /** 47 | * The activator lifecycle plugin for the Appraise UI extensions, plus a couple 48 | * of utility routines. 49 | */ 50 | public class AppraiseUiPlugin extends AbstractUIPlugin { 51 | // The plug-in ID 52 | public static final String PLUGIN_ID = "com.google.appraise.eclipse.ui"; // $NON-NLS-1$ 53 | 54 | // The id of the review task marker in the editor. 55 | public static final String REVIEW_TASK_MARKER_ID = "com.google.appraise.eclipse.ui.reviewtask"; 56 | 57 | // The shared instance 58 | private static AppraiseUiPlugin plugin; 59 | 60 | /** 61 | * The constructor 62 | */ 63 | public AppraiseUiPlugin() {} 64 | 65 | @Override 66 | public void start(BundleContext context) throws Exception { 67 | super.start(context); 68 | plugin = this; 69 | } 70 | 71 | @Override 72 | public void stop(BundleContext context) throws Exception { 73 | plugin = null; 74 | super.stop(context); 75 | } 76 | 77 | /** 78 | * Returns the shared instance 79 | * 80 | * @return the shared instance 81 | */ 82 | public static AppraiseUiPlugin getDefault() { 83 | return plugin; 84 | } 85 | 86 | /** 87 | * Helper method to log errors as {@link IStatus}. 88 | */ 89 | public static void logError(String message, Exception e) { 90 | IStatus status = 91 | new Status(IStatus.ERROR, AppraiseUiPlugin.PLUGIN_ID, "Error reading commit " + message, e); 92 | getDefault().getLog().log(status); 93 | } 94 | 95 | public static void logError(String message) { 96 | IStatus status = 97 | new Status(IStatus.ERROR, AppraiseUiPlugin.PLUGIN_ID, "Error reading commit " + message); 98 | getDefault().getLog().log(status); 99 | } 100 | 101 | /** 102 | * Helper method to write a comment into the active task. Does nothing if 103 | * there is no active task, or the active task is not a Appraise review. 104 | */ 105 | public void writeCommentForActiveTask(ReviewComment comment) { 106 | ITask activeTask = TasksUi.getTaskActivityManager().getActiveTask(); 107 | if (activeTask == null) { 108 | return; 109 | } 110 | if (!AppraiseTaskMapper.APPRAISE_REVIEW_TASK_KIND.equals(activeTask.getTaskKind())) { 111 | return; 112 | } 113 | 114 | TaskRepository taskRepository = TasksUi.getRepositoryManager().getRepository( 115 | AppraiseConnectorPlugin.CONNECTOR_KIND, activeTask.getRepositoryUrl()); 116 | try { 117 | AppraisePluginReviewClient client = new AppraisePluginReviewClient(taskRepository); 118 | client.writeComment(activeTask.getTaskId(), comment); 119 | } catch (GitClientException e) { 120 | AppraiseUiPlugin.logError("Error writing comment for " + activeTask.getTaskId(), e); 121 | } 122 | } 123 | 124 | /** 125 | * Returns the current Git branch, which in the detached head state (should 126 | * be true in the review workflow) will be the commit id. 127 | */ 128 | public String getCurrentCommit() { 129 | ITask activeTask = TasksUi.getTaskActivityManager().getActiveTask(); 130 | TaskRepository taskRepository = TasksUi.getRepositoryManager().getRepository( 131 | AppraiseConnectorPlugin.CONNECTOR_KIND, activeTask.getRepositoryUrl()); 132 | Repository repo = AppraisePluginUtils.getGitRepoForRepository(taskRepository); 133 | try { 134 | return repo.getBranch(); 135 | } catch (IOException e) { 136 | AppraiseUiPlugin.logError("Failed to retrive git branch", e); 137 | return null; 138 | } 139 | } 140 | 141 | /** 142 | * Helper method to open the given file in the workspace editor. 143 | */ 144 | public static void openFileInEditor(String filePath, TaskRepository taskRepository) { 145 | Repository repository = AppraisePluginUtils.getGitRepoForRepository(taskRepository); 146 | IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); 147 | String fullPath = 148 | new Path(repository.getWorkTree().getAbsolutePath()).append(filePath).toOSString(); 149 | File file = new File(fullPath); 150 | if (!file.exists()) { 151 | AppraiseUiPlugin.logError("File to open not found: " + fullPath); 152 | return; 153 | } 154 | IWorkbenchPage page = window.getActivePage(); 155 | IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); 156 | IFile fileResource = root.getFileForLocation(new Path(file.getAbsolutePath())); 157 | if (fileResource != null) { 158 | try { 159 | IDE.openEditor(page, fileResource, OpenStrategy.activateOnOpen()); 160 | } catch (PartInitException e) { 161 | AppraiseUiPlugin.logError("Failed to open editor for " + filePath, e); 162 | } 163 | } else { 164 | IFileStore store = EFS.getLocalFileSystem().getStore(new Path(file.getAbsolutePath())); 165 | try { 166 | IDE.openEditor(page, new FileStoreEditorInput(store), EditorsUI.DEFAULT_TEXT_EDITOR_ID); 167 | } catch (PartInitException e) { 168 | AppraiseUiPlugin.logError("Failed to open editor for " + filePath, e); 169 | } 170 | } 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /ui/src/com/google/appraise/eclipse/ui/Author.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Google and others. 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Eclipse Public License v1.0 5 | * which accompanies this distribution, and is available at 6 | * http://www.eclipse.org/legal/epl-v10.html 7 | * 8 | * Contributors: 9 | * Scott McMaster - initial implementation 10 | *******************************************************************************/ 11 | package com.google.appraise.eclipse.ui; 12 | 13 | import org.eclipse.ui.views.markers.MarkerField; 14 | import org.eclipse.ui.views.markers.MarkerItem; 15 | 16 | /** 17 | * Custom marker view field for review comment authors. 18 | */ 19 | public class Author extends MarkerField { 20 | @Override 21 | public String getValue(MarkerItem item) { 22 | return item.getAttributeValue(ReviewMarkerAttributes.REVIEW_AUTHOR_MARKER_ATTRIBUTE, "Unknown"); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /ui/src/com/google/appraise/eclipse/ui/DateTime.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Google and others. 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Eclipse Public License v1.0 5 | * which accompanies this distribution, and is available at 6 | * http://www.eclipse.org/legal/epl-v10.html 7 | * 8 | * Contributors: 9 | * Scott McMaster - initial implementation 10 | *******************************************************************************/ 11 | package com.google.appraise.eclipse.ui; 12 | 13 | import java.text.DateFormat; 14 | 15 | import org.eclipse.ui.views.markers.MarkerField; 16 | import org.eclipse.ui.views.markers.MarkerItem; 17 | 18 | /** 19 | * Custom marker view field for review comment timestamps. 20 | */ 21 | public class DateTime extends MarkerField { 22 | @Override 23 | public String getValue(MarkerItem item) { 24 | String value = 25 | item.getAttributeValue(ReviewMarkerAttributes.REVIEW_DATETIME_MARKER_ATTRIBUTE, ""); 26 | if (!value.isEmpty()) { 27 | DateFormat df = DateFormat.getDateTimeInstance(); 28 | return df.format(Long.parseLong(value)); 29 | } 30 | return "Unknown"; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /ui/src/com/google/appraise/eclipse/ui/EditCommentDialog.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Google and others. 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Eclipse Public License v1.0 5 | * which accompanies this distribution, and is available at 6 | * http://www.eclipse.org/legal/epl-v10.html 7 | * 8 | * Contributors: 9 | * Scott McMaster - initial implementation 10 | *******************************************************************************/ 11 | package com.google.appraise.eclipse.ui; 12 | 13 | import org.eclipse.jface.dialogs.IMessageProvider; 14 | import org.eclipse.jface.dialogs.TitleAreaDialog; 15 | import org.eclipse.swt.SWT; 16 | import org.eclipse.swt.layout.GridData; 17 | import org.eclipse.swt.layout.GridLayout; 18 | import org.eclipse.swt.widgets.Button; 19 | import org.eclipse.swt.widgets.Combo; 20 | import org.eclipse.swt.widgets.Composite; 21 | import org.eclipse.swt.widgets.Control; 22 | import org.eclipse.swt.widgets.Label; 23 | import org.eclipse.swt.widgets.Shell; 24 | import org.eclipse.swt.widgets.Text; 25 | 26 | /** 27 | * Modal dialog to collect new review comments and edit existing ones. 28 | * Pops up over the text editor when there is an active review. 29 | */ 30 | public class EditCommentDialog extends TitleAreaDialog { 31 | public enum Mode { 32 | NEW, 33 | REPLY 34 | } 35 | 36 | private Text commentText; 37 | private Combo resolvedCombo; 38 | private Button resolvedCheck; 39 | 40 | private String comment; 41 | private Boolean resolved; 42 | private String replyToText; 43 | private String replyToAuthor; 44 | 45 | private final Mode mode; 46 | 47 | public EditCommentDialog(Shell parentShell, Mode mode) { 48 | super(parentShell); 49 | this.mode = mode; 50 | } 51 | 52 | @Override 53 | public void create() { 54 | super.create(); 55 | setTitle("Review Comment"); 56 | setMessage("Enter the new review comment", IMessageProvider.INFORMATION); 57 | } 58 | 59 | @Override 60 | protected Control createDialogArea(Composite parent) { 61 | Composite area = (Composite) super.createDialogArea(parent); 62 | Composite container = new Composite(area, SWT.NONE); 63 | container.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); 64 | GridLayout layout = new GridLayout(2, false); 65 | container.setLayout(layout); 66 | 67 | if (mode == Mode.REPLY) { 68 | createReplyToAuthor(container); 69 | createReplyToText(container); 70 | } 71 | createCommentText(container); 72 | createResolvedWidget(container); 73 | commentText.setFocus(); 74 | return area; 75 | } 76 | 77 | private void createReplyToText(Composite container) { 78 | Label lblReplyToText = new Label(container, SWT.NONE); 79 | lblReplyToText.setText("Said:"); 80 | GridData replyToLblLayoutData = new GridData(); 81 | replyToLblLayoutData.verticalAlignment = GridData.BEGINNING; 82 | lblReplyToText.setLayoutData(replyToLblLayoutData); 83 | 84 | Text replyToTextData = new Text(container, SWT.MULTI | SWT.READ_ONLY); 85 | replyToTextData.setText(replyToText); 86 | } 87 | 88 | private void createReplyToAuthor(Composite container) { 89 | Label lblReplyToAuthor = new Label(container, SWT.NONE); 90 | lblReplyToAuthor.setText("Reviewer:"); 91 | Text replyToAuthorData = new Text(container, SWT.MULTI | SWT.READ_ONLY); 92 | replyToAuthorData.setText(replyToAuthor); 93 | } 94 | 95 | private void createCommentText(Composite container) { 96 | Label lblComment = new Label(container, SWT.NONE); 97 | if (mode == Mode.NEW) { 98 | lblComment.setText("Comment:"); 99 | } else { 100 | lblComment.setText("Reply:"); 101 | } 102 | GridData commentLblLayoutData = new GridData(); 103 | commentLblLayoutData.verticalAlignment = GridData.BEGINNING; 104 | lblComment.setLayoutData(commentLblLayoutData); 105 | 106 | GridData commentTextLayoutData = new GridData(); 107 | commentTextLayoutData.grabExcessHorizontalSpace = true; 108 | commentTextLayoutData.grabExcessVerticalSpace = true; 109 | commentTextLayoutData.horizontalAlignment = GridData.FILL; 110 | commentTextLayoutData.verticalAlignment = GridData.FILL; 111 | 112 | commentText = new Text(container, SWT.BORDER | SWT.MULTI); 113 | commentText.setLayoutData(commentTextLayoutData); 114 | } 115 | 116 | private void createResolvedWidget(Composite container) { 117 | Label lbtLastName = new Label(container, SWT.NONE); 118 | lbtLastName.setText("Resolved?"); 119 | 120 | GridData resolvedCheckLayoutData = new GridData(); 121 | if (mode == Mode.REPLY) { 122 | resolvedCheckLayoutData.grabExcessHorizontalSpace = true; 123 | resolvedCheckLayoutData.horizontalAlignment = GridData.FILL; 124 | resolvedCheck = new Button(container, SWT.CHECK); 125 | resolvedCheck.setLayoutData(resolvedCheckLayoutData); 126 | } else { 127 | resolvedCombo = new Combo(container, SWT.READ_ONLY); 128 | resolvedCombo.setItems(new String[] {"", "Yes", "No"}); 129 | resolvedCombo.setLayoutData(resolvedCheckLayoutData); 130 | } 131 | } 132 | 133 | @Override 134 | protected boolean isResizable() { 135 | return true; 136 | } 137 | 138 | private void saveInput() { 139 | comment = commentText.getText(); 140 | String resolvedValue = resolvedCombo.getText(); 141 | if (mode == Mode.REPLY) { 142 | resolved = resolvedCheck.getSelection(); 143 | if (!resolved) { 144 | // Interpret no selection as no resolved value in the comment. 145 | resolved = null; 146 | } 147 | } else { 148 | if ("Yes".equals(resolvedValue)) { 149 | resolved = true; 150 | } else if ("No".equals(resolvedValue)) { 151 | resolved = false; 152 | } else { 153 | resolved = null; 154 | } 155 | } 156 | } 157 | 158 | @Override 159 | protected void okPressed() { 160 | saveInput(); 161 | super.okPressed(); 162 | } 163 | 164 | public String getComment() { 165 | return comment; 166 | } 167 | 168 | public void setComment(String comment) { 169 | this.comment = comment; 170 | } 171 | 172 | public Boolean getResolved() { 173 | return resolved; 174 | } 175 | 176 | public void setResolved(Boolean resolved) { 177 | this.resolved = resolved; 178 | } 179 | 180 | public String getReplyToText() { 181 | return replyToText; 182 | } 183 | 184 | public void setReplyToText(String replyToText) { 185 | this.replyToText = replyToText; 186 | } 187 | 188 | public String getReplyToAuthor() { 189 | return replyToAuthor; 190 | } 191 | 192 | public void setReplyToAuthor(String replyToAuthor) { 193 | this.replyToAuthor = replyToAuthor; 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /ui/src/com/google/appraise/eclipse/ui/Resolved.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Google and others. 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Eclipse Public License v1.0 5 | * which accompanies this distribution, and is available at 6 | * http://www.eclipse.org/legal/epl-v10.html 7 | * 8 | * Contributors: 9 | * Scott McMaster - initial implementation 10 | *******************************************************************************/ 11 | package com.google.appraise.eclipse.ui; 12 | 13 | import org.eclipse.ui.views.markers.MarkerField; 14 | import org.eclipse.ui.views.markers.MarkerItem; 15 | 16 | /** 17 | * Custom marker view field for the review comment resolved field. 18 | */ 19 | public class Resolved extends MarkerField { 20 | @Override 21 | public String getValue(MarkerItem item) { 22 | String result = 23 | item.getAttributeValue(ReviewMarkerAttributes.REVIEW_RESOLVED_MARKER_ATTRIBUTE, ""); 24 | if ("true".equals(result)) { 25 | return "yes"; 26 | } 27 | return ""; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /ui/src/com/google/appraise/eclipse/ui/ReviewMarkerAttributes.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Google and others. 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Eclipse Public License v1.0 5 | * which accompanies this distribution, and is available at 6 | * http://www.eclipse.org/legal/epl-v10.html 7 | * 8 | * Contributors: 9 | * Scott McMaster - initial implementation 10 | *******************************************************************************/ 11 | package com.google.appraise.eclipse.ui; 12 | 13 | /** 14 | * Definitions for the attributes we stick on review IMarkers in the source 15 | * editor. These are mirrored in the reviewmarker extension in plugin.xml. 16 | */ 17 | public class ReviewMarkerAttributes { 18 | 19 | public static final String REVIEW_AUTHOR_MARKER_ATTRIBUTE = "Author"; 20 | 21 | public static final String REVIEW_ID_MARKER_ATTRIBUTE = "Id"; 22 | 23 | public static final String REVIEW_DATETIME_MARKER_ATTRIBUTE = "DateTime"; 24 | 25 | public static final String REVIEW_RESOLVED_MARKER_ATTRIBUTE = "Resolved"; 26 | } 27 | -------------------------------------------------------------------------------- /ui/src/com/google/appraise/eclipse/ui/contextmenus/ReviewMarkerContributionFactory.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Google and others. 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Eclipse Public License v1.0 5 | * which accompanies this distribution, and is available at 6 | * http://www.eclipse.org/legal/epl-v10.html 7 | * 8 | * Contributors: 9 | * Scott McMaster - initial implementation 10 | *******************************************************************************/ 11 | package com.google.appraise.eclipse.ui.contextmenus; 12 | 13 | import com.google.appraise.eclipse.ui.AppraiseUiPlugin; 14 | 15 | import org.eclipse.core.resources.IMarker; 16 | import org.eclipse.core.resources.IResource; 17 | import org.eclipse.core.runtime.CoreException; 18 | import org.eclipse.jface.action.Separator; 19 | import org.eclipse.jface.text.source.IVerticalRulerInfo; 20 | import org.eclipse.ui.PlatformUI; 21 | import org.eclipse.ui.menus.ExtensionContributionFactory; 22 | import org.eclipse.ui.menus.IContributionRoot; 23 | import org.eclipse.ui.part.FileEditorInput; 24 | import org.eclipse.ui.services.IServiceLocator; 25 | import org.eclipse.ui.texteditor.ITextEditor; 26 | 27 | import java.util.ArrayList; 28 | import java.util.List; 29 | 30 | /** 31 | * Adds a menu contribution for the marker popup which will display comments. 32 | */ 33 | public class ReviewMarkerContributionFactory extends ExtensionContributionFactory { 34 | @Override 35 | public void createContributionItems(IServiceLocator serviceLocator, IContributionRoot additions) { 36 | ITextEditor editor = (ITextEditor) 37 | PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getActivePart(); 38 | IVerticalRulerInfo rulerInfo = editor.getAdapter(IVerticalRulerInfo.class); 39 | 40 | try { 41 | List markers = getMarkers(editor, rulerInfo); 42 | additions.addContributionItem(new ReviewMarkerMenuContribution(editor, markers), null); 43 | if (!markers.isEmpty()) { 44 | additions.addContributionItem(new Separator(), null); 45 | } 46 | } catch (CoreException e) { 47 | AppraiseUiPlugin.logError("Error creating marker context menus", e); 48 | } 49 | } 50 | 51 | private List getMarkers(ITextEditor editor, IVerticalRulerInfo rulerInfo) 52 | throws CoreException { 53 | List clickedOnMarkers = new ArrayList(); 54 | for (IMarker marker : getAllMarkers(editor)) { 55 | if (markerHasBeenClicked(marker, rulerInfo)) { 56 | clickedOnMarkers.add(marker); 57 | } 58 | } 59 | 60 | return clickedOnMarkers; 61 | } 62 | 63 | private IMarker[] getAllMarkers(ITextEditor editor) throws CoreException { 64 | return ((FileEditorInput) editor.getEditorInput()) 65 | .getFile() 66 | .findMarkers(AppraiseUiPlugin.REVIEW_TASK_MARKER_ID, true, IResource.DEPTH_ZERO); 67 | } 68 | 69 | private boolean markerHasBeenClicked(IMarker marker, IVerticalRulerInfo rulerInfo) { 70 | return (marker.getAttribute(IMarker.LINE_NUMBER, 0)) 71 | == (rulerInfo.getLineOfLastMouseButtonActivity() + 1); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /ui/src/com/google/appraise/eclipse/ui/contextmenus/ReviewMarkerMenuContribution.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Google and others. 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Eclipse Public License v1.0 5 | * which accompanies this distribution, and is available at 6 | * http://www.eclipse.org/legal/epl-v10.html 7 | * 8 | * Contributors: 9 | * Scott McMaster - initial implementation 10 | *******************************************************************************/ 11 | package com.google.appraise.eclipse.ui.contextmenus; 12 | 13 | import com.google.appraise.eclipse.core.client.data.ReviewComment; 14 | import com.google.appraise.eclipse.core.client.data.ReviewCommentLocation; 15 | import com.google.appraise.eclipse.core.client.data.ReviewCommentLocationRange; 16 | import com.google.appraise.eclipse.ui.EditCommentDialog; 17 | import com.google.appraise.eclipse.ui.ReviewMarkerAttributes; 18 | import com.google.appraise.eclipse.ui.AppraiseUiPlugin; 19 | 20 | import org.eclipse.core.resources.IFile; 21 | import org.eclipse.core.resources.IMarker; 22 | import org.eclipse.jface.action.ContributionItem; 23 | import org.eclipse.jface.window.Window; 24 | import org.eclipse.swt.SWT; 25 | import org.eclipse.swt.events.SelectionAdapter; 26 | import org.eclipse.swt.events.SelectionEvent; 27 | import org.eclipse.swt.widgets.Menu; 28 | import org.eclipse.swt.widgets.MenuItem; 29 | import org.eclipse.swt.widgets.Shell; 30 | import org.eclipse.ui.IEditorInput; 31 | import org.eclipse.ui.PlatformUI; 32 | import org.eclipse.ui.part.FileEditorInput; 33 | import org.eclipse.ui.texteditor.ITextEditor; 34 | 35 | import java.util.List; 36 | 37 | /** 38 | * Creates a marker context menu contribution that organizes all the given 39 | * review comments under a submenu. 40 | */ 41 | public class ReviewMarkerMenuContribution extends ContributionItem { 42 | private final List markers; 43 | private final ITextEditor editor; 44 | 45 | public ReviewMarkerMenuContribution(ITextEditor editor, List markers) { 46 | this.markers = markers; 47 | this.editor = editor; 48 | } 49 | 50 | @Override 51 | public void fill(Menu menu, int index) { 52 | MenuItem submenuItem = new MenuItem(menu, SWT.CASCADE, index); 53 | submenuItem.setText("&Appraise Review Comments"); 54 | Menu submenu = new Menu(menu); 55 | submenuItem.setMenu(submenu); 56 | for (IMarker marker : markers) { 57 | MenuItem menuItem = new MenuItem(submenu, SWT.CHECK); 58 | menuItem.setText(marker.getAttribute(IMarker.MESSAGE, "")); 59 | menuItem.addSelectionListener(createDynamicSelectionListener(marker)); 60 | } 61 | } 62 | 63 | private SelectionAdapter createDynamicSelectionListener(final IMarker marker) { 64 | return new SelectionAdapter() { 65 | @Override 66 | public void widgetSelected(SelectionEvent e) { 67 | replyToComment(marker); 68 | } 69 | }; 70 | } 71 | 72 | /** 73 | * Returns the path to the active file, minus the Eclipse project name. 74 | */ 75 | private String getFilePath() { 76 | IEditorInput input = editor.getEditorInput(); 77 | IFile file = ((FileEditorInput) input).getFile(); 78 | return file.getFullPath().toPortableString().replaceFirst( 79 | "/" + file.getProject().getName(), ""); 80 | } 81 | 82 | /** 83 | * Creates a reply to the comment represented by the given marker. 84 | */ 85 | private void replyToComment(IMarker marker) { 86 | Shell activeShell = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(); 87 | EditCommentDialog dialog = new EditCommentDialog(activeShell, EditCommentDialog.Mode.REPLY); 88 | dialog.setReplyToText(marker.getAttribute(IMarker.MESSAGE, "")); 89 | dialog.setReplyToAuthor( 90 | marker.getAttribute(ReviewMarkerAttributes.REVIEW_AUTHOR_MARKER_ATTRIBUTE, "")); 91 | 92 | dialog.create(); 93 | if (dialog.open() == Window.OK) { 94 | ReviewComment comment = new ReviewComment(); 95 | comment.setDescription(dialog.getComment()); 96 | comment.setResolved(dialog.getResolved()); 97 | String parentCommentId = 98 | marker.getAttribute(ReviewMarkerAttributes.REVIEW_ID_MARKER_ATTRIBUTE, ""); 99 | comment.setParent(parentCommentId); 100 | 101 | ReviewCommentLocation location = new ReviewCommentLocation(); 102 | location.setCommit(AppraiseUiPlugin.getDefault().getCurrentCommit()); 103 | location.setPath(getFilePath()); 104 | ReviewCommentLocationRange range = new ReviewCommentLocationRange(); 105 | range.setStartLine(marker.getAttribute(IMarker.LINE_NUMBER, 0)); 106 | location.setRange(range); 107 | comment.setLocation(location); 108 | AppraiseUiPlugin.getDefault().writeCommentForActiveTask(comment); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /ui/src/com/google/appraise/eclipse/ui/contextmenus/TextEditorContextContributionFactory.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Google and others. 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Eclipse Public License v1.0 5 | * which accompanies this distribution, and is available at 6 | * http://www.eclipse.org/legal/epl-v10.html 7 | * 8 | * Contributors: 9 | * Scott McMaster - initial implementation 10 | *******************************************************************************/ 11 | package com.google.appraise.eclipse.ui.contextmenus; 12 | 13 | import org.eclipse.ui.PlatformUI; 14 | import org.eclipse.ui.menus.ExtensionContributionFactory; 15 | import org.eclipse.ui.menus.IContributionRoot; 16 | import org.eclipse.ui.services.IServiceLocator; 17 | import org.eclipse.ui.texteditor.ITextEditor; 18 | 19 | /** 20 | * Adds a menu contribution for the text editor popup which will allow creation 21 | * of new comments. 22 | */ 23 | public class TextEditorContextContributionFactory extends ExtensionContributionFactory { 24 | @Override 25 | public void createContributionItems(IServiceLocator serviceLocator, IContributionRoot additions) { 26 | ITextEditor editor = (ITextEditor) 27 | PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getActivePart(); 28 | 29 | additions.addContributionItem(new TextEditorContextMenuContribution(editor), null); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /ui/src/com/google/appraise/eclipse/ui/contextmenus/TextEditorContextMenuContribution.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Google and others. 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Eclipse Public License v1.0 5 | * which accompanies this distribution, and is available at 6 | * http://www.eclipse.org/legal/epl-v10.html 7 | * 8 | * Contributors: 9 | * Scott McMaster - initial implementation 10 | *******************************************************************************/ 11 | package com.google.appraise.eclipse.ui.contextmenus; 12 | 13 | import com.google.appraise.eclipse.core.AppraiseTaskMapper; 14 | import com.google.appraise.eclipse.core.client.data.ReviewComment; 15 | import com.google.appraise.eclipse.core.client.data.ReviewCommentLocation; 16 | import com.google.appraise.eclipse.core.client.data.ReviewCommentLocationRange; 17 | import com.google.appraise.eclipse.ui.EditCommentDialog; 18 | import com.google.appraise.eclipse.ui.AppraiseUiPlugin; 19 | 20 | import org.eclipse.core.resources.IFile; 21 | import org.eclipse.jface.action.ContributionItem; 22 | import org.eclipse.jface.text.BadLocationException; 23 | import org.eclipse.jface.text.IDocument; 24 | import org.eclipse.jface.text.ITextSelection; 25 | import org.eclipse.jface.window.Window; 26 | import org.eclipse.mylyn.tasks.core.ITask; 27 | import org.eclipse.mylyn.tasks.ui.TasksUi; 28 | import org.eclipse.swt.SWT; 29 | import org.eclipse.swt.events.SelectionAdapter; 30 | import org.eclipse.swt.events.SelectionEvent; 31 | import org.eclipse.swt.widgets.Menu; 32 | import org.eclipse.swt.widgets.MenuItem; 33 | import org.eclipse.swt.widgets.Shell; 34 | import org.eclipse.ui.IEditorInput; 35 | import org.eclipse.ui.PlatformUI; 36 | import org.eclipse.ui.part.FileEditorInput; 37 | import org.eclipse.ui.texteditor.IDocumentProvider; 38 | import org.eclipse.ui.texteditor.ITextEditor; 39 | 40 | /** 41 | * Creates a context menu for text editors that allows creation of new 42 | * review comments. 43 | */ 44 | public class TextEditorContextMenuContribution extends ContributionItem { 45 | private final ITextEditor editor; 46 | 47 | public TextEditorContextMenuContribution(ITextEditor editor) { 48 | this.editor = editor; 49 | } 50 | 51 | @Override 52 | public void fill(Menu menu, int index) { 53 | MenuItem submenuItem = new MenuItem(menu, SWT.CASCADE, index); 54 | submenuItem.setText("&Appraise Review Comments"); 55 | Menu submenu = new Menu(menu); 56 | submenuItem.setMenu(submenu); 57 | 58 | MenuItem reviewCommentMenuItem = new MenuItem(submenu, SWT.CHECK); 59 | reviewCommentMenuItem.setText("New &Review Comment..."); 60 | reviewCommentMenuItem.addSelectionListener(createReviewCommentSelectionListener()); 61 | 62 | MenuItem fileCommentMenuItem = new MenuItem(submenu, SWT.CHECK); 63 | fileCommentMenuItem.setText("New &File Comment..."); 64 | fileCommentMenuItem.addSelectionListener(createFileCommentSelectionListener()); 65 | 66 | MenuItem fileLineCommentMenuItem = new MenuItem(submenu, SWT.CHECK); 67 | fileLineCommentMenuItem.setText("New &Line Comment..."); 68 | fileLineCommentMenuItem.addSelectionListener(createFileLineCommentSelectionListener()); 69 | 70 | // Can only add Appraise comments if there is an active Appraise review task. 71 | ITask activeTask = TasksUi.getTaskActivityManager().getActiveTask(); 72 | submenuItem.setEnabled(activeTask != null 73 | && AppraiseTaskMapper.APPRAISE_REVIEW_TASK_KIND.equals(activeTask.getTaskKind())); 74 | } 75 | 76 | private SelectionAdapter createReviewCommentSelectionListener() { 77 | return new SelectionAdapter() { 78 | @Override 79 | public void widgetSelected(SelectionEvent e) { 80 | createComment(null, 0); 81 | } 82 | }; 83 | } 84 | 85 | private SelectionAdapter createFileCommentSelectionListener() { 86 | return new SelectionAdapter() { 87 | @Override 88 | public void widgetSelected(SelectionEvent e) { 89 | createComment(getFilePath(), 0); 90 | } 91 | }; 92 | } 93 | 94 | private SelectionAdapter createFileLineCommentSelectionListener() { 95 | return new SelectionAdapter() { 96 | @Override 97 | public void widgetSelected(SelectionEvent e) { 98 | createComment(getFilePath(), getSelectedLineNumber()); 99 | } 100 | }; 101 | } 102 | 103 | /** 104 | * Returns the 1-based line number of the current text editor selection, 105 | * or 0 if some weird error occurs. If there is a multi-line selection. the 106 | * first line will get returned. Which is what we want for the purposes of 107 | * creating a review comment. 108 | */ 109 | private int getSelectedLineNumber() { 110 | ITextSelection textSelection = 111 | (ITextSelection) editor.getSite().getSelectionProvider().getSelection(); 112 | IDocumentProvider provider = editor.getDocumentProvider(); 113 | IDocument document = provider.getDocument(editor.getEditorInput()); 114 | int offset = textSelection.getOffset(); 115 | try { 116 | return document.getLineOfOffset(offset) + 1; 117 | } catch (BadLocationException e) { 118 | return 0; 119 | } 120 | } 121 | 122 | /** 123 | * Returns the path to the active file, minus the Eclipse project name. 124 | */ 125 | private String getFilePath() { 126 | IEditorInput input = editor.getEditorInput(); 127 | IFile file = ((FileEditorInput) input).getFile(); 128 | return file.getFullPath().toPortableString().replaceFirst( 129 | "/" + file.getProject().getName(), ""); 130 | } 131 | 132 | /** 133 | * Creates a comment in the active review at the given line number and file, 134 | * a file-level comment if the line number is 0, or a review-level comment 135 | * if there is no file specified. 136 | */ 137 | private void createComment(String path, int lineNumber) { 138 | Shell activeShell = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(); 139 | EditCommentDialog dialog = new EditCommentDialog(activeShell, EditCommentDialog.Mode.NEW); 140 | dialog.create(); 141 | if (dialog.open() == Window.OK) { 142 | ReviewComment comment = new ReviewComment(); 143 | comment.setDescription(dialog.getComment()); 144 | comment.setResolved(dialog.getResolved()); 145 | 146 | ReviewCommentLocation location = new ReviewCommentLocation(); 147 | location.setCommit(AppraiseUiPlugin.getDefault().getCurrentCommit()); 148 | if (path != null && !path.isEmpty()) { 149 | location.setPath(path); 150 | } 151 | if (lineNumber > 0) { 152 | ReviewCommentLocationRange range = new ReviewCommentLocationRange(); 153 | range.setStartLine(lineNumber); 154 | location.setRange(range); 155 | } 156 | comment.setLocation(location); 157 | AppraiseUiPlugin.getDefault().writeCommentForActiveTask(comment); 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /ui/src/com/google/appraise/eclipse/ui/editor/AppraiseDiffViewerPart.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Google and others. 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Eclipse Public License v1.0 5 | * which accompanies this distribution, and is available at 6 | * http://www.eclipse.org/legal/epl-v10.html 7 | * 8 | * Contributors: 9 | * Scott McMaster - initial implementation 10 | *******************************************************************************/ 11 | package com.google.appraise.eclipse.ui.editor; 12 | 13 | import com.google.appraise.eclipse.core.AppraiseReviewTaskSchema; 14 | 15 | import org.eclipse.jface.layout.GridDataFactory; 16 | import org.eclipse.jgit.diff.DiffEntry.ChangeType; 17 | import org.eclipse.mylyn.commons.ui.FillWidthLayout; 18 | import org.eclipse.mylyn.internal.tasks.ui.editors.EditorUtil; 19 | import org.eclipse.mylyn.tasks.core.data.TaskAttribute; 20 | import org.eclipse.mylyn.tasks.core.data.TaskData; 21 | import org.eclipse.mylyn.tasks.ui.editors.AbstractAttributeEditor; 22 | import org.eclipse.mylyn.tasks.ui.editors.AbstractTaskEditorPart; 23 | import org.eclipse.swt.layout.GridData; 24 | import org.eclipse.swt.layout.GridLayout; 25 | import org.eclipse.swt.widgets.Composite; 26 | import org.eclipse.ui.forms.IFormColors; 27 | import org.eclipse.ui.forms.events.ExpansionAdapter; 28 | import org.eclipse.ui.forms.events.ExpansionEvent; 29 | import org.eclipse.ui.forms.widgets.ExpandableComposite; 30 | import org.eclipse.ui.forms.widgets.FormToolkit; 31 | import org.eclipse.ui.forms.widgets.Section; 32 | 33 | import java.util.List; 34 | 35 | /** 36 | * Implements a full-review diff viewer inside a task editor part. 37 | */ 38 | public class AppraiseDiffViewerPart extends AbstractTaskEditorPart { 39 | private static final String KEY_DIFF_ATTRIBUTE_EDITOR = "diffviewer"; 40 | 41 | /** 42 | * Gets the TYPE_DIFF task attributes off the task data. 43 | */ 44 | private List getDiffTaskAttributes() { 45 | TaskData taskData = getModel().getTaskData(); 46 | return taskData.getAttributeMapper().getAttributesByType(taskData, 47 | AppraiseReviewTaskSchema.TYPE_DIFF); 48 | } 49 | 50 | @Override 51 | public void createControl(Composite parent, final FormToolkit toolkit) { 52 | final List diffTaskAttributes = getDiffTaskAttributes(); 53 | 54 | if (diffTaskAttributes == null || diffTaskAttributes.isEmpty()) { 55 | return; 56 | } 57 | int style = ExpandableComposite.TWISTIE | ExpandableComposite.SHORT_TITLE_BAR; 58 | final Section groupSection = toolkit.createSection(parent, style); 59 | groupSection.setText("Changes (" + diffTaskAttributes.size() + ')'); 60 | groupSection.clientVerticalSpacing = 0; 61 | groupSection.setForeground(toolkit.getColors().getColor(IFormColors.TITLE)); 62 | 63 | if (groupSection.isExpanded()) { 64 | addDiffViewersToSection(toolkit, diffTaskAttributes, groupSection); 65 | } else { 66 | groupSection.addExpansionListener(new ExpansionAdapter() { 67 | @Override 68 | public void expansionStateChanged(ExpansionEvent e) { 69 | if (groupSection.getClient() == null) { 70 | try { 71 | getTaskEditorPage().setReflow(false); 72 | addDiffViewersToSection(toolkit, diffTaskAttributes, groupSection); 73 | } finally { 74 | getTaskEditorPage().setReflow(true); 75 | } 76 | getTaskEditorPage().reflow(); 77 | } 78 | } 79 | }); 80 | } 81 | } 82 | 83 | /** 84 | * Helper method to put the individual diff viewers into the given group section. 85 | */ 86 | private void addDiffViewersToSection(final FormToolkit toolkit, 87 | final List diffTaskAttributes, final Section groupSection) { 88 | Composite composite = createDiffViewers(groupSection, toolkit, diffTaskAttributes); 89 | groupSection.setClient(composite); 90 | } 91 | 92 | /** 93 | * Creates the controls to display diffs and returns the resulting composite. 94 | */ 95 | private Composite createDiffViewers(Composite parent, final FormToolkit toolkit, 96 | List diffTaskAttributes) { 97 | Composite composite = toolkit.createComposite(parent); 98 | GridLayout contentLayout = new GridLayout(); 99 | contentLayout.marginHeight = 0; 100 | contentLayout.marginWidth = 0; 101 | composite.setLayout(contentLayout); 102 | 103 | for (final TaskAttribute diffTaskAttribute : diffTaskAttributes) { 104 | createDiffViewer(toolkit, composite, diffTaskAttribute); 105 | } 106 | 107 | return composite; 108 | } 109 | 110 | /** 111 | * Creates an individual diff viewer in the given composite. 112 | */ 113 | private void createDiffViewer(final FormToolkit toolkit, Composite composite, 114 | final TaskAttribute diffTaskAttribute) { 115 | 116 | int style = ExpandableComposite.TREE_NODE | ExpandableComposite.LEFT_TEXT_CLIENT_ALIGNMENT 117 | | ExpandableComposite.COMPACT; 118 | ExpandableComposite diffComposite = toolkit.createExpandableComposite(composite, style); 119 | diffComposite.clientVerticalSpacing = 0; 120 | diffComposite.setLayout(new GridLayout()); 121 | diffComposite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); 122 | diffComposite.setTitleBarForeground(toolkit.getColors().getColor(IFormColors.TITLE)); 123 | diffComposite.setText(calculateDiffChangeHeader(diffTaskAttribute)); 124 | 125 | final Composite diffViewerComposite = toolkit.createComposite(diffComposite); 126 | diffComposite.setClient(diffViewerComposite); 127 | diffViewerComposite.setLayout( 128 | new FillWidthLayout(EditorUtil.getLayoutAdvisor(getTaskEditorPage()), 15, 0, 0, 3)); 129 | 130 | diffComposite.addExpansionListener(new ExpansionAdapter() { 131 | @Override 132 | public void expansionStateChanged(ExpansionEvent event) { 133 | expandCollapseDiff(toolkit, diffViewerComposite, diffTaskAttribute, event.getState()); 134 | } 135 | }); 136 | GridDataFactory.fillDefaults().grab(true, false).applyTo(diffComposite); 137 | } 138 | 139 | /** 140 | * Finds the appropriate title for an individual change given its various attributes. 141 | */ 142 | private String calculateDiffChangeHeader(TaskAttribute diffTaskAttribute) { 143 | String newPath = diffTaskAttribute.getAttribute(AppraiseReviewTaskSchema.DIFF_NEWPATH).getValue(); 144 | String oldPath = diffTaskAttribute.getAttribute(AppraiseReviewTaskSchema.DIFF_OLDPATH).getValue(); 145 | String type = diffTaskAttribute.getAttribute(AppraiseReviewTaskSchema.DIFF_TYPE).getValue(); 146 | ChangeType changeType = ChangeType.MODIFY; 147 | try { 148 | changeType = ChangeType.valueOf(type); 149 | } catch (Exception e) { 150 | } 151 | 152 | switch (changeType) { 153 | case ADD: 154 | return newPath + " (Added)"; 155 | 156 | case COPY: 157 | return newPath + " (Copied from " + oldPath + ")"; 158 | 159 | case DELETE: 160 | return newPath + " (Deleted)"; 161 | 162 | case RENAME: 163 | return newPath + " (was " + oldPath + ")"; 164 | 165 | case MODIFY: 166 | default: 167 | return newPath + " (Modified)"; 168 | } 169 | } 170 | 171 | private void expandCollapseDiff(FormToolkit toolkit, Composite composite, 172 | TaskAttribute diffTaskAttribute, boolean expanded) { 173 | if (expanded && composite.getData(KEY_DIFF_ATTRIBUTE_EDITOR) == null) { 174 | AbstractAttributeEditor editor = createAttributeEditor(diffTaskAttribute); 175 | if (editor != null) { 176 | editor.setDecorationEnabled(false); 177 | editor.createControl(composite, toolkit); 178 | composite.setData(KEY_DIFF_ATTRIBUTE_EDITOR, editor); 179 | getTaskEditorPage().getAttributeEditorToolkit().adapt(editor); 180 | getTaskEditorPage().reflow(); 181 | } 182 | } else if (!expanded && composite.getData(KEY_DIFF_ATTRIBUTE_EDITOR) != null) { 183 | AbstractAttributeEditor editor = 184 | (AbstractAttributeEditor) composite.getData(KEY_DIFF_ATTRIBUTE_EDITOR); 185 | editor.getControl().dispose(); 186 | composite.setData(KEY_DIFF_ATTRIBUTE_EDITOR, null); 187 | getTaskEditorPage().reflow(); 188 | } 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /ui/src/com/google/appraise/eclipse/ui/editor/AppraiseReviewAttributePart.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Google and others. 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Eclipse Public License v1.0 5 | * which accompanies this distribution, and is available at 6 | * http://www.eclipse.org/legal/epl-v10.html 7 | * 8 | * Contributors: 9 | * Scott McMaster - initial implementation 10 | *******************************************************************************/ 11 | package com.google.appraise.eclipse.ui.editor; 12 | 13 | import com.google.appraise.eclipse.core.AppraiseReviewTaskSchema; 14 | 15 | import org.eclipse.mylyn.internal.tasks.ui.editors.TaskEditorAttributePart; 16 | import org.eclipse.mylyn.tasks.core.data.TaskAttribute; 17 | 18 | import java.util.Collection; 19 | import java.util.List; 20 | 21 | /** 22 | * The custom attributes part. 23 | */ 24 | public class AppraiseReviewAttributePart extends TaskEditorAttributePart { 25 | private final AppraiseReviewTaskSchema schema; 26 | 27 | public AppraiseReviewAttributePart() { 28 | super(); 29 | schema = AppraiseReviewTaskSchema.getDefault(); 30 | } 31 | 32 | @Override 33 | protected List getOverlayAttributes() { 34 | List attributes = super.getOverlayAttributes(); 35 | TaskAttribute root = getModel().getTaskData().getRoot(); 36 | 37 | TaskAttribute requester = root.getAttribute(schema.REQUESTER.getKey()); 38 | attributes.add(requester); 39 | 40 | TaskAttribute reviewers = root.getAttribute(schema.REVIEWERS.getKey()); 41 | attributes.add(reviewers); 42 | 43 | return attributes; 44 | } 45 | 46 | @Override 47 | protected Collection getAttributes() { 48 | List attributes = super.getOverlayAttributes(); 49 | TaskAttribute root = getModel().getTaskData().getRoot(); 50 | 51 | TaskAttribute reviewCommit = root.getAttribute(schema.REVIEW_COMMIT.getKey()); 52 | attributes.add(reviewCommit); 53 | 54 | TaskAttribute requester = root.getAttribute(schema.REQUESTER.getKey()); 55 | attributes.add(requester); 56 | 57 | TaskAttribute reviewers = root.getAttribute(schema.REVIEWERS.getKey()); 58 | attributes.add(reviewers); 59 | 60 | TaskAttribute reviewRef = root.getAttribute(schema.REVIEW_REF.getKey()); 61 | attributes.add(reviewRef); 62 | 63 | TaskAttribute submitted = root.getAttribute(schema.IS_SUBMITTED.getKey()); 64 | attributes.add(submitted); 65 | 66 | TaskAttribute targetRef = root.getAttribute(schema.TARGET_REF.getKey()); 67 | attributes.add(targetRef); 68 | 69 | return attributes; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /ui/src/com/google/appraise/eclipse/ui/editor/AppraiseReviewTaskActivationListener.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Google and others. 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Eclipse Public License v1.0 5 | * which accompanies this distribution, and is available at 6 | * http://www.eclipse.org/legal/epl-v10.html 7 | * 8 | * Contributors: 9 | * Scott McMaster - initial implementation 10 | *******************************************************************************/ 11 | package com.google.appraise.eclipse.ui.editor; 12 | 13 | import com.google.appraise.eclipse.core.AppraiseConnectorPlugin; 14 | import com.google.appraise.eclipse.core.AppraisePluginUtils; 15 | import com.google.appraise.eclipse.core.AppraiseReviewTaskSchema; 16 | import com.google.appraise.eclipse.ui.AppraiseUiPlugin; 17 | 18 | import org.eclipse.core.resources.IProject; 19 | import org.eclipse.core.resources.IResource; 20 | import org.eclipse.core.runtime.CoreException; 21 | import org.eclipse.jface.dialogs.MessageDialog; 22 | import org.eclipse.jgit.api.Git; 23 | import org.eclipse.jgit.api.errors.RefNotFoundException; 24 | import org.eclipse.jgit.lib.Repository; 25 | import org.eclipse.mylyn.tasks.core.ITask; 26 | import org.eclipse.mylyn.tasks.core.TaskActivationAdapter; 27 | import org.eclipse.mylyn.tasks.core.TaskRepository; 28 | import org.eclipse.mylyn.tasks.core.data.TaskData; 29 | import org.eclipse.mylyn.tasks.ui.TasksUi; 30 | 31 | /** 32 | * Handles task activiation and deactiviation for Appraise tasks. 33 | */ 34 | public class AppraiseReviewTaskActivationListener extends TaskActivationAdapter { 35 | /** 36 | * Hold on to the name of the branch we will go to when a task is deactivated. 37 | */ 38 | private String previousBranch; 39 | 40 | @Override 41 | public void taskActivated(ITask task) { 42 | if (task == null) { 43 | return; 44 | } 45 | 46 | TaskData taskData = loadTaskData(task); 47 | if (taskData == null) { 48 | AppraiseUiPlugin.logError("Failed to load task data for " + task.getTaskId()); 49 | return; 50 | } 51 | 52 | TaskRepository taskRepository = TasksUi.getRepositoryManager().getRepository( 53 | AppraiseConnectorPlugin.CONNECTOR_KIND, taskData.getRepositoryUrl()); 54 | 55 | previousBranch = null; 56 | String reviewBranch = 57 | taskData.getRoot() 58 | .getAttribute(AppraiseReviewTaskSchema.getDefault().REVIEW_REF.getKey()) 59 | .getValue(); 60 | if (reviewBranch != null && !reviewBranch.isEmpty()) { 61 | promptSwitchToReviewBranch(taskRepository, reviewBranch); 62 | } 63 | 64 | new ReviewMarkerManager(taskRepository, taskData).createMarkers(); 65 | } 66 | 67 | /** 68 | * Asks the user if they want to switch to the review branch, and performs 69 | * the switch if so. 70 | */ 71 | private void promptSwitchToReviewBranch(TaskRepository taskRepository, String reviewBranch) { 72 | MessageDialog dialog = new MessageDialog(null, "Appraise Review", null, 73 | "Do you want to switch to the review branch (" + reviewBranch + ")", MessageDialog.QUESTION, 74 | new String[] {"Yes", "No"}, 0); 75 | int result = dialog.open(); 76 | if (result == 0) { 77 | Repository repo = AppraisePluginUtils.getGitRepoForRepository(taskRepository); 78 | try (Git git = new Git(repo)) { 79 | previousBranch = repo.getFullBranch(); 80 | git.checkout().setName(reviewBranch).call(); 81 | } catch (RefNotFoundException rnfe) { 82 | MessageDialog alert = new MessageDialog(null, "Oops", null, 83 | "Branch " + reviewBranch + " not found", MessageDialog.INFORMATION, new String[] {"OK"}, 0); 84 | alert.open(); 85 | } catch (Exception e) { 86 | AppraiseUiPlugin.logError("Unable to switch to review branch: " + reviewBranch, e); 87 | } 88 | } 89 | } 90 | 91 | @Override 92 | public void taskDeactivated(ITask task) { 93 | if (task == null) { 94 | return; 95 | } 96 | 97 | TaskData taskData = loadTaskData(task); 98 | TaskRepository taskRepository = TasksUi.getRepositoryManager().getRepository( 99 | AppraiseConnectorPlugin.CONNECTOR_KIND, taskData.getRepositoryUrl()); 100 | 101 | if (previousBranch != null) { 102 | Repository repo = AppraisePluginUtils.getGitRepoForRepository(taskRepository); 103 | try (Git git = new Git(repo)) { 104 | git.checkout().setName(previousBranch).call(); 105 | } catch (Exception e) { 106 | AppraiseUiPlugin.logError("Unable to switch to previous branch: " + previousBranch, e); 107 | } 108 | } 109 | 110 | int depth = IResource.DEPTH_INFINITE; 111 | IProject project = AppraisePluginUtils.getProjectForRepository(taskRepository); 112 | try { 113 | project.deleteMarkers(AppraiseUiPlugin.REVIEW_TASK_MARKER_ID, true, depth); 114 | } catch (CoreException e) { 115 | AppraiseUiPlugin.logError("Error deleting review markers for task " + task.getTaskId(), e); 116 | } 117 | } 118 | 119 | /** 120 | * Gets the task data for the given task. 121 | */ 122 | private TaskData loadTaskData(ITask task) { 123 | TaskData taskData = null; 124 | try { 125 | taskData = TasksUi.getTaskDataManager().getTaskData(task); 126 | } catch (CoreException e) { 127 | AppraiseUiPlugin.logError("Failed to load task data " + task.getTaskId(), e); 128 | } 129 | return taskData; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /ui/src/com/google/appraise/eclipse/ui/editor/AppraiseReviewTaskEditorPage.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Google and others. 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Eclipse Public License v1.0 5 | * which accompanies this distribution, and is available at 6 | * http://www.eclipse.org/legal/epl-v10.html 7 | * 8 | * Contributors: 9 | * Scott McMaster - initial implementation 10 | *******************************************************************************/ 11 | package com.google.appraise.eclipse.ui.editor; 12 | 13 | import com.google.appraise.eclipse.core.AppraiseConnectorPlugin; 14 | import com.google.appraise.eclipse.core.AppraiseReviewTaskSchema; 15 | import com.google.appraise.eclipse.ui.AppraiseUiPlugin; 16 | 17 | import org.eclipse.mylyn.tasks.core.data.TaskAttribute; 18 | import org.eclipse.mylyn.tasks.ui.editors.AbstractAttributeEditor; 19 | import org.eclipse.mylyn.tasks.ui.editors.AbstractTaskEditorPage; 20 | import org.eclipse.mylyn.tasks.ui.editors.AbstractTaskEditorPart; 21 | import org.eclipse.mylyn.tasks.ui.editors.AttributeEditorFactory; 22 | import org.eclipse.mylyn.tasks.ui.editors.LayoutHint; 23 | import org.eclipse.mylyn.tasks.ui.editors.LayoutHint.ColumnSpan; 24 | import org.eclipse.mylyn.tasks.ui.editors.LayoutHint.RowSpan; 25 | import org.eclipse.mylyn.tasks.ui.editors.TaskEditor; 26 | import org.eclipse.mylyn.tasks.ui.editors.TaskEditorPartDescriptor; 27 | 28 | import java.net.URL; 29 | import java.util.LinkedHashSet; 30 | import java.util.Set; 31 | 32 | /** 33 | * Custom task editor page for Appraise reviews. 34 | */ 35 | public class AppraiseReviewTaskEditorPage extends AbstractTaskEditorPage { 36 | static final URL baseURL = AppraiseUiPlugin.getDefault().getBundle().getEntry("/icons/"); 37 | 38 | public AppraiseReviewTaskEditorPage(TaskEditor parentEditor) { 39 | super(parentEditor, AppraiseReviewTaskEditorPage.class.getName(), "Appraise Review", 40 | AppraiseConnectorPlugin.CONNECTOR_KIND); 41 | setNeedsPrivateSection(false); 42 | setNeedsSubmit(true); 43 | setNeedsSubmitButton(true); 44 | } 45 | 46 | @Override 47 | protected AttributeEditorFactory createAttributeEditorFactory() { 48 | return new AttributeEditorFactory(getModel(), getTaskRepository(), getEditorSite()) { 49 | @Override 50 | public AbstractAttributeEditor createEditor(String type, TaskAttribute taskAttribute) { 51 | if (taskAttribute.getId().equals( 52 | AppraiseReviewTaskSchema.getDefault().REVIEW_COMMIT.getKey())) { 53 | return new CommitAttributeEditor(getModel(), taskAttribute); 54 | } else if (taskAttribute.getId().equals(TaskAttribute.COMMENT_TEXT)) { 55 | return new CommentAttributeEditor(getModel(), taskAttribute); 56 | } else if (taskAttribute.getId().startsWith(AppraiseReviewTaskSchema.PREFIX_DIFF)) { 57 | return new DiffAttributeEditor(getModel(), taskAttribute); 58 | } else { 59 | AbstractAttributeEditor editor = super.createEditor(type, taskAttribute); 60 | editor.setLayoutHint(new LayoutHint(RowSpan.SINGLE, ColumnSpan.SINGLE)); 61 | return editor; 62 | } 63 | } 64 | }; 65 | } 66 | 67 | @Override 68 | protected Set createPartDescriptors() { 69 | Set descriptors = new LinkedHashSet(); 70 | Set superDescriptors = super.createPartDescriptors(); 71 | TaskEditorPartDescriptor commentsDescriptor = null; 72 | TaskEditorPartDescriptor newCommentsDescriptor = null; 73 | for (TaskEditorPartDescriptor taskEditorPartDescriptor : superDescriptors) { 74 | TaskEditorPartDescriptor descriptor = getNewDescriptor(taskEditorPartDescriptor); 75 | if (descriptor != null) { 76 | if (ID_PART_COMMENTS.equals(descriptor.getId())) { 77 | commentsDescriptor = descriptor; 78 | } else if (ID_PART_NEW_COMMENT.equals(descriptor.getId())) { 79 | newCommentsDescriptor = descriptor; 80 | } else { 81 | descriptors.add(descriptor); 82 | } 83 | } 84 | } 85 | descriptors.add(new TaskEditorPartDescriptor("com.google.appraise.eclipse.ui.diff") { 86 | @Override 87 | public AbstractTaskEditorPart createPart() { 88 | return new AppraiseDiffViewerPart(); 89 | } 90 | }); 91 | if (commentsDescriptor != null) { 92 | descriptors.add(commentsDescriptor); 93 | } 94 | if (newCommentsDescriptor != null) { 95 | descriptors.add(newCommentsDescriptor); 96 | } 97 | return descriptors; 98 | } 99 | 100 | private TaskEditorPartDescriptor getNewDescriptor(TaskEditorPartDescriptor descriptor) { 101 | if (PATH_ACTIONS.equals(descriptor.getPath()) || PATH_PEOPLE.equals(descriptor.getPath())) { 102 | return null; 103 | } else if (ID_PART_ATTRIBUTES.equals(descriptor.getId())) { 104 | return new TaskEditorPartDescriptor(ID_PART_ATTRIBUTES) { 105 | @Override 106 | public AbstractTaskEditorPart createPart() { 107 | return new AppraiseReviewAttributePart(); 108 | } 109 | }; 110 | } else if (ID_PART_COMMENTS.equals(descriptor.getId())) { 111 | return new TaskEditorPartDescriptor(ID_PART_COMMENTS) { 112 | @Override 113 | public AbstractTaskEditorPart createPart() { 114 | return new AppraiseTaskEditorCommentPart(); 115 | } 116 | }; 117 | } 118 | return descriptor; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /ui/src/com/google/appraise/eclipse/ui/editor/AppraiseReviewTaskEditorPageFactory.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Google and others. 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Eclipse Public License v1.0 5 | * which accompanies this distribution, and is available at 6 | * http://www.eclipse.org/legal/epl-v10.html 7 | * 8 | * Contributors: 9 | * Scott McMaster - initial implementation 10 | *******************************************************************************/ 11 | package com.google.appraise.eclipse.ui.editor; 12 | 13 | import com.google.appraise.eclipse.core.AppraiseConnectorPlugin; 14 | 15 | import org.eclipse.mylyn.commons.ui.CommonImages; 16 | import org.eclipse.mylyn.tasks.ui.TasksUiImages; 17 | import org.eclipse.mylyn.tasks.ui.TasksUiUtil; 18 | import org.eclipse.mylyn.tasks.ui.editors.AbstractTaskEditorPageFactory; 19 | import org.eclipse.mylyn.tasks.ui.editors.TaskEditor; 20 | import org.eclipse.mylyn.tasks.ui.editors.TaskEditorInput; 21 | import org.eclipse.swt.graphics.Image; 22 | import org.eclipse.ui.forms.editor.IFormPage; 23 | 24 | /** 25 | * Creates the main review viewing/editing page. 26 | */ 27 | public class AppraiseReviewTaskEditorPageFactory extends AbstractTaskEditorPageFactory { 28 | @Override 29 | public boolean canCreatePageFor(TaskEditorInput input) { 30 | return (input.getTask().getConnectorKind().equals(AppraiseConnectorPlugin.CONNECTOR_KIND) 31 | || TasksUiUtil.isOutgoingNewTask(input.getTask(), AppraiseConnectorPlugin.CONNECTOR_KIND)); 32 | } 33 | 34 | @Override 35 | public IFormPage createPage(TaskEditor parentEditor) { 36 | return new AppraiseReviewTaskEditorPage(parentEditor); 37 | } 38 | 39 | @Override 40 | public Image getPageImage() { 41 | return CommonImages.getImage(TasksUiImages.REPOSITORY_SMALL); 42 | } 43 | 44 | @Override 45 | public String getPageText() { 46 | return "Appraise Review"; 47 | } 48 | 49 | @Override 50 | public int getPriority() { 51 | return PRIORITY_TASK; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /ui/src/com/google/appraise/eclipse/ui/editor/AppraiseTaskEditorCommentPart.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Google and others. 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Eclipse Public License v1.0 5 | * which accompanies this distribution, and is available at 6 | * http://www.eclipse.org/legal/epl-v10.html 7 | * 8 | * Contributors: 9 | * Scott McMaster - initial implementation 10 | *******************************************************************************/ 11 | package com.google.appraise.eclipse.ui.editor; 12 | 13 | import com.google.appraise.eclipse.core.AppraiseReviewTaskSchema; 14 | import com.google.appraise.eclipse.ui.AppraiseUiPlugin; 15 | 16 | import org.eclipse.jface.action.Action; 17 | import org.eclipse.jface.action.ToolBarManager; 18 | import org.eclipse.jface.resource.ImageDescriptor; 19 | import org.eclipse.mylyn.internal.tasks.core.TaskComment; 20 | import org.eclipse.mylyn.internal.tasks.ui.editors.TaskEditorCommentPart; 21 | import org.eclipse.mylyn.tasks.core.data.TaskAttribute; 22 | import org.eclipse.mylyn.tasks.ui.editors.AbstractAttributeEditor; 23 | import org.eclipse.mylyn.tasks.ui.editors.AttributeEditorFactory; 24 | 25 | import java.net.MalformedURLException; 26 | import java.net.URL; 27 | 28 | /** 29 | * Custom comment editor part for Appraise. Supports extra things like the "resolved" flag. 30 | */ 31 | public class AppraiseTaskEditorCommentPart extends TaskEditorCommentPart { 32 | @Override 33 | protected AbstractAttributeEditor createAttributeEditor(TaskAttribute attribute) { 34 | if (attribute == null) { 35 | return null; 36 | } 37 | 38 | String type = attribute.getMetaData().getType(); 39 | if (type != null) { 40 | AttributeEditorFactory attributeEditorFactory = 41 | getTaskEditorPage().getAttributeEditorFactory(); 42 | AbstractAttributeEditor editor = attributeEditorFactory.createEditor(type, attribute); 43 | return editor; 44 | } 45 | return null; 46 | } 47 | 48 | /** 49 | * Add a "resolved" indicator to the toolbar title. It is implemented as a 50 | * fake action. 51 | */ 52 | @Override 53 | protected void addActionsToToolbarTitle( 54 | ToolBarManager toolBarManager, TaskComment taskComment, CommentViewer commentViewer) { 55 | final TaskAttribute resolvedAttr = taskComment.getTaskAttribute().getAttribute( 56 | AppraiseReviewTaskSchema.COMMENT_RESOLVED_ATTRIBUTE); 57 | if (resolvedAttr != null && "true".equals(resolvedAttr.getValue())) { 58 | Action resolveAction = new Action() { 59 | @Override 60 | public String getToolTipText() { 61 | return "Resolved?"; 62 | } 63 | 64 | @Override 65 | public ImageDescriptor getImageDescriptor() { 66 | try { 67 | return ImageDescriptor.createFromURL( 68 | new URL(AppraiseReviewTaskEditorPage.baseURL, "greencheck.png")); 69 | } catch (MalformedURLException e) { 70 | AppraiseUiPlugin.logError("Unable to create icon image", e); 71 | } 72 | return null; 73 | } 74 | }; 75 | toolBarManager.add(resolveAction); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /ui/src/com/google/appraise/eclipse/ui/editor/CommentAttributeEditor.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Google and others. 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Eclipse Public License v1.0 5 | * which accompanies this distribution, and is available at 6 | * http://www.eclipse.org/legal/epl-v10.html 7 | * 8 | * Contributors: 9 | * Scott McMaster - initial implementation 10 | *******************************************************************************/ 11 | package com.google.appraise.eclipse.ui.editor; 12 | 13 | import com.google.appraise.eclipse.core.AppraiseReviewTaskSchema; 14 | import com.google.appraise.eclipse.ui.AppraiseUiPlugin; 15 | 16 | import org.eclipse.mylyn.tasks.core.data.TaskAttribute; 17 | import org.eclipse.mylyn.tasks.core.data.TaskDataModel; 18 | import org.eclipse.mylyn.tasks.ui.editors.AbstractAttributeEditor; 19 | import org.eclipse.swt.SWT; 20 | import org.eclipse.swt.layout.RowLayout; 21 | import org.eclipse.swt.widgets.Composite; 22 | import org.eclipse.swt.widgets.Event; 23 | import org.eclipse.swt.widgets.Label; 24 | import org.eclipse.swt.widgets.Link; 25 | import org.eclipse.swt.widgets.Listener; 26 | import org.eclipse.ui.forms.widgets.FormToolkit; 27 | 28 | /** 29 | * Custom attribute editor for review comments. 30 | */ 31 | public class CommentAttributeEditor extends AbstractAttributeEditor { 32 | private Link fileLink; 33 | private Label commitLabel; 34 | private Label label; 35 | 36 | public CommentAttributeEditor(TaskDataModel manager, TaskAttribute taskAttribute) { 37 | super(manager, taskAttribute); 38 | } 39 | 40 | @Override 41 | public void createControl(final Composite parent, FormToolkit toolkit) { 42 | Composite composite = new Composite(parent, SWT.NONE); 43 | RowLayout layout = new RowLayout(SWT.VERTICAL); 44 | layout.wrap = true; 45 | layout.fill = true; 46 | layout.justify = false; 47 | composite.setLayout(layout); 48 | 49 | label = new Label(composite, SWT.NONE); 50 | label.setText(getValue()); 51 | 52 | final String commit = getCommit(); 53 | final String filePath = getFilePath(); 54 | final int lineNo = getLineNumber(); 55 | if (filePath != null && !filePath.isEmpty()) { 56 | fileLink = new Link(composite, SWT.BORDER); 57 | fileLink.setText("" + filePath + '(' + lineNo + ")"); 58 | fileLink.addListener(SWT.Selection, new Listener() { 59 | @Override 60 | public void handleEvent(Event event) { 61 | AppraiseUiPlugin.openFileInEditor(filePath, getModel().getTaskRepository()); 62 | } 63 | }); 64 | } 65 | 66 | if (commit != null) { 67 | commitLabel = new Label(composite, SWT.BORDER); 68 | commitLabel.setText(commit); 69 | } 70 | 71 | composite.pack(); 72 | setControl(composite); 73 | } 74 | 75 | private String getFilePath() { 76 | final TaskAttribute locationFileAttr = getTaskAttribute().getParentAttribute().getAttribute( 77 | AppraiseReviewTaskSchema.COMMENT_LOCATION_FILE); 78 | if (locationFileAttr != null) { 79 | return locationFileAttr.getValue(); 80 | } 81 | return null; 82 | } 83 | 84 | private int getLineNumber() { 85 | final TaskAttribute locationFileAttr = getTaskAttribute().getParentAttribute().getAttribute( 86 | AppraiseReviewTaskSchema.COMMENT_LOCATION_LINE); 87 | if (locationFileAttr != null && locationFileAttr.getValue() != null && 88 | locationFileAttr.getValue().length() > 0) { 89 | return Integer.parseInt(locationFileAttr.getValue()); 90 | } 91 | return 0; 92 | } 93 | 94 | private String getCommit() { 95 | final TaskAttribute locationCommitAttr = getTaskAttribute().getParentAttribute().getAttribute( 96 | AppraiseReviewTaskSchema.COMMENT_LOCATION_COMMIT); 97 | if (locationCommitAttr != null) { 98 | return locationCommitAttr.getValue(); 99 | } 100 | return null; 101 | } 102 | 103 | public String getValue() { 104 | return getAttributeMapper().getValue(getTaskAttribute()); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /ui/src/com/google/appraise/eclipse/ui/editor/CommitAttributeEditor.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Google and others. 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Eclipse Public License v1.0 5 | * which accompanies this distribution, and is available at 6 | * http://www.eclipse.org/legal/epl-v10.html 7 | * 8 | * Contributors: 9 | * Scott McMaster - initial implementation 10 | *******************************************************************************/ 11 | package com.google.appraise.eclipse.ui.editor; 12 | 13 | import com.google.appraise.eclipse.core.AppraisePluginUtils; 14 | import com.google.appraise.eclipse.ui.AppraiseUiPlugin; 15 | 16 | import org.eclipse.egit.ui.internal.commit.CommitEditor; 17 | import org.eclipse.egit.ui.internal.commit.RepositoryCommit; 18 | import org.eclipse.jface.dialogs.MessageDialog; 19 | import org.eclipse.jgit.errors.IncorrectObjectTypeException; 20 | import org.eclipse.jgit.errors.MissingObjectException; 21 | import org.eclipse.jgit.lib.ObjectId; 22 | import org.eclipse.jgit.lib.Repository; 23 | import org.eclipse.jgit.revwalk.RevCommit; 24 | import org.eclipse.jgit.revwalk.RevWalk; 25 | import org.eclipse.mylyn.tasks.core.data.TaskAttribute; 26 | import org.eclipse.mylyn.tasks.core.data.TaskDataModel; 27 | import org.eclipse.mylyn.tasks.ui.editors.AbstractAttributeEditor; 28 | import org.eclipse.mylyn.tasks.ui.editors.LayoutHint; 29 | import org.eclipse.mylyn.tasks.ui.editors.LayoutHint.ColumnSpan; 30 | import org.eclipse.mylyn.tasks.ui.editors.LayoutHint.RowSpan; 31 | import org.eclipse.swt.SWT; 32 | import org.eclipse.swt.widgets.Composite; 33 | import org.eclipse.swt.widgets.Event; 34 | import org.eclipse.swt.widgets.Link; 35 | import org.eclipse.swt.widgets.Listener; 36 | import org.eclipse.ui.forms.widgets.FormToolkit; 37 | 38 | import java.io.IOException; 39 | 40 | /** 41 | * Custom attribute editor for the review commit. Handles jumping to the commit 42 | * in the Eclipse commit editor. 43 | */ 44 | public class CommitAttributeEditor extends AbstractAttributeEditor { 45 | private Link link; 46 | 47 | public CommitAttributeEditor(TaskDataModel manager, TaskAttribute taskAttribute) { 48 | super(manager, taskAttribute); 49 | setLayoutHint(new LayoutHint(RowSpan.SINGLE, ColumnSpan.SINGLE)); 50 | } 51 | 52 | @Override 53 | public void createControl(final Composite parent, FormToolkit toolkit) { 54 | link = new Link(parent, SWT.BORDER); 55 | link.setText("" + getValue() + ""); 56 | link.addListener(SWT.Selection, new Listener() { 57 | @Override 58 | public void handleEvent(Event event) { 59 | try { 60 | RepositoryCommit commit = getCommit(); 61 | if (commit != null) { 62 | CommitEditor.openQuiet(commit); 63 | } else { 64 | MessageDialog alert = new MessageDialog(parent.getShell(), "Oops", null, 65 | "Commit " + getValue() + " not found", MessageDialog.ERROR, new String[] {"OK"}, 0); 66 | alert.open(); 67 | } 68 | } catch (IOException e) { 69 | AppraiseUiPlugin.logError("Error reading commit " + getValue(), e); 70 | } 71 | } 72 | }); 73 | setControl(link); 74 | } 75 | 76 | public String getValue() { 77 | return getAttributeMapper().getValue(getTaskAttribute()); 78 | } 79 | 80 | private RepositoryCommit getCommit() throws IOException { 81 | Repository repository = 82 | AppraisePluginUtils.getGitRepoForRepository(this.getModel().getTaskRepository()); 83 | try (RevWalk revWalk = new RevWalk(repository)) { 84 | RevCommit commit = revWalk.parseCommit(ObjectId.fromString(getValue())); 85 | if (commit != null) { 86 | return new RepositoryCommit(repository, commit); 87 | } 88 | } catch (MissingObjectException e) { 89 | AppraiseUiPlugin.logError("Commit not found " + getValue(), e); 90 | } catch (IncorrectObjectTypeException e) { 91 | AppraiseUiPlugin.logError("Not a commit " + getValue(), e); 92 | } 93 | return null; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /ui/src/com/google/appraise/eclipse/ui/editor/DiffAttributeEditor.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Google and others. 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Eclipse Public License v1.0 5 | * which accompanies this distribution, and is available at 6 | * http://www.eclipse.org/legal/epl-v10.html 7 | * 8 | * Contributors: 9 | * Scott McMaster - initial implementation 10 | *******************************************************************************/ 11 | package com.google.appraise.eclipse.ui.editor; 12 | 13 | import static org.eclipse.egit.ui.UIPreferences.THEME_DiffAddBackgroundColor; 14 | import static org.eclipse.egit.ui.UIPreferences.THEME_DiffAddForegroundColor; 15 | import static org.eclipse.egit.ui.UIPreferences.THEME_DiffHeadlineBackgroundColor; 16 | import static org.eclipse.egit.ui.UIPreferences.THEME_DiffHeadlineFont; 17 | import static org.eclipse.egit.ui.UIPreferences.THEME_DiffHeadlineForegroundColor; 18 | import static org.eclipse.egit.ui.UIPreferences.THEME_DiffHunkBackgroundColor; 19 | import static org.eclipse.egit.ui.UIPreferences.THEME_DiffHunkForegroundColor; 20 | import static org.eclipse.egit.ui.UIPreferences.THEME_DiffRemoveBackgroundColor; 21 | import static org.eclipse.egit.ui.UIPreferences.THEME_DiffRemoveForegroundColor; 22 | 23 | import com.google.appraise.eclipse.core.AppraiseReviewTaskSchema; 24 | import com.google.appraise.eclipse.ui.AppraiseUiPlugin; 25 | 26 | import org.eclipse.jface.resource.ColorRegistry; 27 | import org.eclipse.jface.resource.FontRegistry; 28 | import org.eclipse.mylyn.tasks.core.data.TaskAttribute; 29 | import org.eclipse.mylyn.tasks.core.data.TaskDataModel; 30 | import org.eclipse.mylyn.tasks.ui.editors.AbstractAttributeEditor; 31 | import org.eclipse.mylyn.tasks.ui.editors.LayoutHint; 32 | import org.eclipse.mylyn.tasks.ui.editors.LayoutHint.ColumnSpan; 33 | import org.eclipse.mylyn.tasks.ui.editors.LayoutHint.RowSpan; 34 | import org.eclipse.swt.SWT; 35 | import org.eclipse.swt.custom.StyleRange; 36 | import org.eclipse.swt.custom.StyledText; 37 | import org.eclipse.swt.layout.GridData; 38 | import org.eclipse.swt.layout.GridLayout; 39 | import org.eclipse.swt.widgets.Composite; 40 | import org.eclipse.swt.widgets.Event; 41 | import org.eclipse.swt.widgets.Link; 42 | import org.eclipse.swt.widgets.Listener; 43 | import org.eclipse.ui.PlatformUI; 44 | import org.eclipse.ui.forms.widgets.FormToolkit; 45 | 46 | import java.util.ArrayList; 47 | import java.util.List; 48 | 49 | /** 50 | * Attribute editor for individual files in a diff. 51 | */ 52 | public class DiffAttributeEditor extends AbstractAttributeEditor { 53 | 54 | /** 55 | * Line type used for diff highlighting. 56 | */ 57 | private enum DiffLineType { 58 | ADD, 59 | REMOVE, 60 | OTHER, 61 | HUNK, 62 | HEADLINE 63 | } 64 | 65 | public DiffAttributeEditor(TaskDataModel manager, TaskAttribute taskAttribute) { 66 | super(manager, taskAttribute); 67 | setLayoutHint(new LayoutHint(RowSpan.MULTIPLE, ColumnSpan.MULTIPLE)); 68 | } 69 | 70 | @Override 71 | public void createControl(final Composite parent, FormToolkit toolkit) { 72 | Composite composite = new Composite(parent, SWT.NONE); 73 | GridLayout layout = new GridLayout(1, false); 74 | composite.setLayout(layout); 75 | 76 | final String filePath = 77 | getTaskAttribute().getAttribute(AppraiseReviewTaskSchema.DIFF_NEWPATH).getValue(); 78 | 79 | Link fileLink = new Link(composite, SWT.BORDER); 80 | fileLink.setText("View in Workspace"); 81 | fileLink.addListener(SWT.Selection, new Listener() { 82 | @Override 83 | public void handleEvent(Event event) { 84 | AppraiseUiPlugin.openFileInEditor(filePath, getModel().getTaskRepository()); 85 | } 86 | }); 87 | 88 | final String diffText = 89 | getTaskAttribute().getAttribute(AppraiseReviewTaskSchema.DIFF_TEXT).getValue(); 90 | 91 | final StyledText text = new StyledText(composite, SWT.MULTI | SWT.LEFT | SWT.BORDER | SWT.READ_ONLY); 92 | text.setText(diffText); 93 | text.setStyleRanges(getStyleRangesForDiffText(diffText)); 94 | 95 | GridData diffTextGridData = new GridData(); 96 | diffTextGridData.grabExcessHorizontalSpace = true; 97 | diffTextGridData.horizontalAlignment = SWT.FILL; 98 | text.setLayoutData(diffTextGridData); 99 | 100 | composite.pack(); 101 | setControl(composite); 102 | } 103 | 104 | /** 105 | * Figures out all the combined {@link StyleRange}s for the given unified-diff text. 106 | */ 107 | private StyleRange[] getStyleRangesForDiffText(String diffText) { 108 | List ranges = new ArrayList<>(); 109 | if (diffText != null) { 110 | String[] lines = diffText.split("\n"); 111 | 112 | // Read the first line. 113 | DiffLineType curLineType = getDiffLineType(lines[0]); 114 | StyleRange currentRange = initDiffStyleRangeForLineType(curLineType, 0); 115 | int charsRead = lines[0].length() + 1; 116 | currentRange.length = charsRead; 117 | 118 | // Read the subsequent lines. 119 | int lineNum = 1; 120 | for (; lineNum < lines.length; lineNum++) { 121 | String curLine = lines[lineNum]; 122 | DiffLineType newLineType = getDiffLineType(curLine); 123 | if (newLineType != curLineType) { 124 | currentRange.length = charsRead - currentRange.start; 125 | curLineType = newLineType; 126 | ranges.add(currentRange); 127 | currentRange = initDiffStyleRangeForLineType(curLineType, charsRead); 128 | } 129 | charsRead += curLine.length(); 130 | charsRead++; // for the newline. 131 | } 132 | 133 | // Close out the last range. 134 | currentRange.length = charsRead - currentRange.start; 135 | ranges.add(currentRange); 136 | } 137 | return ranges.toArray(new StyleRange[ranges.size()]); 138 | } 139 | 140 | /** 141 | * Calculates the type of line given the prefix in unified-diff format. 142 | * TODO: This might be overly simplistic... 143 | */ 144 | private DiffLineType getDiffLineType(String line) { 145 | if (line.startsWith("@@")) { 146 | return DiffLineType.HUNK; 147 | } else if (line.startsWith("diff --git")) { 148 | return DiffLineType.HEADLINE; 149 | } else if (line.startsWith("+")) { 150 | return DiffLineType.ADD; 151 | } else if (line.startsWith("-")) { 152 | return DiffLineType.REMOVE; 153 | } else { 154 | return DiffLineType.OTHER; 155 | } 156 | } 157 | 158 | /** 159 | * Starts a new {@link StyleRange} given a specific line type. 160 | */ 161 | private StyleRange initDiffStyleRangeForLineType(DiffLineType lineType, int startTextOffset) { 162 | ColorRegistry reg = 163 | PlatformUI.getWorkbench().getThemeManager().getCurrentTheme().getColorRegistry(); 164 | StyleRange range = new StyleRange(); 165 | range.start = startTextOffset; 166 | switch (lineType) { 167 | case ADD: 168 | range.foreground = reg.get(THEME_DiffAddForegroundColor); 169 | range.background = reg.get(THEME_DiffAddBackgroundColor); 170 | break; 171 | case REMOVE: 172 | range.foreground = reg.get(THEME_DiffRemoveForegroundColor); 173 | range.background = reg.get(THEME_DiffRemoveBackgroundColor); 174 | break; 175 | case HUNK: 176 | range.foreground = reg.get(THEME_DiffHunkForegroundColor); 177 | range.background = reg.get(THEME_DiffHunkBackgroundColor); 178 | break; 179 | case HEADLINE: 180 | range.foreground = reg.get(THEME_DiffHeadlineForegroundColor); 181 | range.background = reg.get(THEME_DiffHeadlineBackgroundColor); 182 | FontRegistry fontReg = 183 | PlatformUI.getWorkbench().getThemeManager().getCurrentTheme().getFontRegistry(); 184 | range.font = fontReg.get(THEME_DiffHeadlineFont); 185 | break; 186 | default: 187 | break; 188 | } 189 | return range; 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /ui/src/com/google/appraise/eclipse/ui/editor/ReviewMarkerManager.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Google and others. 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Eclipse Public License v1.0 5 | * which accompanies this distribution, and is available at 6 | * http://www.eclipse.org/legal/epl-v10.html 7 | * 8 | * Contributors: 9 | * Scott McMaster - initial implementation 10 | *******************************************************************************/ 11 | package com.google.appraise.eclipse.ui.editor; 12 | 13 | import com.google.appraise.eclipse.core.AppraisePluginUtils; 14 | import com.google.appraise.eclipse.core.AppraiseReviewTaskSchema; 15 | import com.google.appraise.eclipse.ui.AppraiseUiPlugin; 16 | import com.google.appraise.eclipse.ui.ReviewMarkerAttributes; 17 | 18 | import org.eclipse.core.resources.IMarker; 19 | import org.eclipse.core.resources.IProject; 20 | import org.eclipse.core.resources.IResource; 21 | import org.eclipse.core.runtime.CoreException; 22 | import org.eclipse.mylyn.tasks.core.TaskRepository; 23 | import org.eclipse.mylyn.tasks.core.data.TaskAttribute; 24 | import org.eclipse.mylyn.tasks.core.data.TaskData; 25 | 26 | import java.util.List; 27 | 28 | /** 29 | * Creates markers for a given task. 30 | */ 31 | public class ReviewMarkerManager { 32 | 33 | private final TaskData taskData; 34 | private final TaskRepository taskRepository; 35 | 36 | public ReviewMarkerManager(TaskRepository taskRepository, TaskData taskData) { 37 | this.taskRepository = taskRepository; 38 | this.taskData = taskData; 39 | } 40 | 41 | /** 42 | * Creates all the markers for this task data instance. 43 | */ 44 | public void createMarkers() { 45 | List comments = 46 | taskData.getAttributeMapper().getAttributesByType(taskData, TaskAttribute.TYPE_COMMENT); 47 | for (TaskAttribute commentAttr : comments) { 48 | markComment(taskRepository, commentAttr, taskData.getTaskId()); 49 | } 50 | } 51 | 52 | /** 53 | * Adds a marker for the given comment, assuming it has a location attached. 54 | */ 55 | private void markComment( 56 | TaskRepository taskRepository, TaskAttribute commentAttr, String taskId) { 57 | IProject project = AppraisePluginUtils.getProjectForRepository(taskRepository); 58 | 59 | String filePath = getFilePath(commentAttr); 60 | IResource resource = project; 61 | if (filePath != null) { 62 | resource = project.getFile(filePath); 63 | if (resource == null || !resource.exists()) { 64 | return; 65 | } 66 | } 67 | 68 | try { 69 | IMarker marker = resource.createMarker(AppraiseUiPlugin.REVIEW_TASK_MARKER_ID); 70 | marker.setAttribute(IMarker.MESSAGE, getMessage(commentAttr)); 71 | marker.setAttribute(IMarker.TRANSIENT, true); 72 | if (filePath != null) { 73 | marker.setAttribute(IMarker.LINE_NUMBER, getLineNumber(commentAttr)); 74 | } 75 | marker.setAttribute(IMarker.USER_EDITABLE, false); 76 | TaskAttribute authorAttribute = commentAttr.getMappedAttribute(TaskAttribute.COMMENT_AUTHOR); 77 | if (authorAttribute != null) { 78 | marker.setAttribute( 79 | ReviewMarkerAttributes.REVIEW_AUTHOR_MARKER_ATTRIBUTE, authorAttribute.getValue()); 80 | } 81 | marker.setAttribute( 82 | ReviewMarkerAttributes.REVIEW_DATETIME_MARKER_ATTRIBUTE, 83 | commentAttr.getMappedAttribute(TaskAttribute.COMMENT_DATE).getValue()); 84 | marker.setAttribute( 85 | ReviewMarkerAttributes.REVIEW_ID_MARKER_ATTRIBUTE, getCommentId(commentAttr)); 86 | marker.setAttribute( 87 | ReviewMarkerAttributes.REVIEW_RESOLVED_MARKER_ATTRIBUTE, 88 | getResolvedDisplayText(commentAttr)); 89 | marker.setAttribute("TaskId", taskId); 90 | } catch (CoreException e) { 91 | AppraiseUiPlugin.logError("Failed to create marker at " + filePath, e); 92 | } 93 | } 94 | 95 | private String getFilePath(TaskAttribute commentAttr) { 96 | final TaskAttribute locationFileAttr = 97 | commentAttr.getAttribute(AppraiseReviewTaskSchema.COMMENT_LOCATION_FILE); 98 | if (locationFileAttr != null) { 99 | return locationFileAttr.getValue(); 100 | } 101 | return null; 102 | } 103 | 104 | private String getMessage(TaskAttribute commentAttr) { 105 | final TaskAttribute msgAttr = commentAttr.getAttribute(TaskAttribute.COMMENT_TEXT); 106 | if (msgAttr != null) { 107 | return msgAttr.getValue(); 108 | } 109 | return ""; 110 | } 111 | 112 | private int getLineNumber(TaskAttribute commentAttr) { 113 | final TaskAttribute locationFileAttr = 114 | commentAttr.getAttribute(AppraiseReviewTaskSchema.COMMENT_LOCATION_LINE); 115 | if (locationFileAttr != null) { 116 | return Integer.parseInt(locationFileAttr.getValue()); 117 | } 118 | return 0; 119 | } 120 | 121 | private String getResolvedDisplayText(TaskAttribute commentAttr) { 122 | final TaskAttribute resolvedAttr = 123 | commentAttr.getAttribute(AppraiseReviewTaskSchema.COMMENT_RESOLVED_ATTRIBUTE); 124 | if (resolvedAttr != null) { 125 | if ("true".equals(resolvedAttr.getValue())) { 126 | return "Yes"; 127 | } else { 128 | return "No"; 129 | } 130 | } 131 | return ""; 132 | } 133 | 134 | private String getCommentId(TaskAttribute commentAttr) { 135 | final TaskAttribute idAttr = 136 | commentAttr.getAttribute(AppraiseReviewTaskSchema.COMMENT_ID_ATTRIBUTE); 137 | if (idAttr != null) { 138 | return idAttr.getValue(); 139 | } 140 | return null; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /ui/src/com/google/appraise/eclipse/ui/wizard/AppraiseRepositorySettingsPage.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2015 Google and others. 3 | * All rights reserved. This program and the accompanying materials 4 | * are made available under the terms of the Eclipse Public License v1.0 5 | * which accompanies this distribution, and is available at 6 | * http://www.eclipse.org/legal/epl-v10.html 7 | * 8 | * Contributors: 9 | * Scott McMaster - initial implementation 10 | *******************************************************************************/ 11 | package com.google.appraise.eclipse.ui.wizard; 12 | 13 | import com.google.appraise.eclipse.core.AppraiseConnectorPlugin; 14 | 15 | import org.eclipse.core.resources.IProject; 16 | import org.eclipse.core.resources.ResourcesPlugin; 17 | import org.eclipse.mylyn.internal.tasks.core.RepositoryTemplateManager; 18 | import org.eclipse.mylyn.internal.tasks.ui.TasksUiPlugin; 19 | import org.eclipse.mylyn.tasks.core.RepositoryTemplate; 20 | import org.eclipse.mylyn.tasks.core.TaskRepository; 21 | import org.eclipse.mylyn.tasks.ui.wizards.AbstractRepositorySettingsPage; 22 | import org.eclipse.swt.widgets.Composite; 23 | 24 | import java.util.ArrayList; 25 | import java.util.List; 26 | 27 | /** 28 | * Custom repository settings page for Appraise. 29 | */ 30 | public class AppraiseRepositorySettingsPage extends AbstractRepositorySettingsPage { 31 | public AppraiseRepositorySettingsPage(TaskRepository repository) { 32 | super("Appraise Repository", "Appraise Repository Connector", repository); 33 | setNeedsAnonymousLogin(false); 34 | setNeedsEncoding(false); 35 | setNeedsTimeZone(false); 36 | setNeedsAdvanced(false); 37 | setNeedsProxy(false); 38 | setNeedsValidation(false); 39 | setNeedsValidateOnFinish(false); 40 | setNeedsAdvanced(false); 41 | setNeedsHttpAuth(false); 42 | } 43 | 44 | @Override 45 | public String getConnectorKind() { 46 | return AppraiseConnectorPlugin.CONNECTOR_KIND; 47 | } 48 | 49 | private List getProjects() { 50 | List projects = new ArrayList(); 51 | for (IProject project : ResourcesPlugin.getWorkspace().getRoot().getProjects()) { 52 | projects.add(project); 53 | } 54 | return projects; 55 | } 56 | 57 | @Override 58 | protected void repositoryTemplateSelected(RepositoryTemplate template) { 59 | repositoryLabelEditor.setStringValue(template.label); 60 | setUrl(template.repositoryUrl); 61 | setUserId(System.getProperty("user.name")); 62 | setPassword(""); 63 | 64 | getContainer().updateButtons(); 65 | } 66 | 67 | @Override 68 | protected void addRepositoryTemplatesToServerUrlCombo() { 69 | RepositoryTemplateManager templateManager = TasksUiPlugin.getRepositoryTemplateManager(); 70 | 71 | for (IProject project : getProjects()) { 72 | String label = "Appraise: " + project.getName(); 73 | String repositoryUrl = "http://appraise.google.com/" + project.getName(); 74 | 75 | boolean found = false; 76 | for (RepositoryTemplate existing : 77 | templateManager.getTemplates(AppraiseConnectorPlugin.CONNECTOR_KIND)) { 78 | if (repositoryUrl.equals(existing.repositoryUrl)) { 79 | found = true; 80 | break; 81 | } 82 | } 83 | 84 | if (!found) { 85 | RepositoryTemplate template = new RepositoryTemplate( 86 | label, repositoryUrl, null, null, null, null, null, null, true, false); 87 | TasksUiPlugin.getRepositoryTemplateManager().addTemplate( 88 | AppraiseConnectorPlugin.CONNECTOR_KIND, template); 89 | } 90 | } 91 | super.addRepositoryTemplatesToServerUrlCombo(); 92 | } 93 | 94 | @Override 95 | protected void createAdditionalControls(Composite parent) {} 96 | 97 | @Override 98 | public void createControl(Composite parent) { 99 | super.createControl(parent); 100 | addRepositoryTemplatesToServerUrlCombo(); 101 | } 102 | 103 | @Override 104 | protected boolean isValidUrl(String url) { 105 | return url != null && url.trim().length() > 0; 106 | } 107 | 108 | @Override 109 | protected Validator getValidator(TaskRepository repository) { 110 | return null; 111 | } 112 | } 113 | --------------------------------------------------------------------------------