87 Commits

Author SHA1 Message Date
81827bee9a Fix typo in custom policy handler 2022-08-22 17:07:04 +02:00
Anna Sirota
c041cfa1bd Fix alignment of custom badges
Reviewed By: Sergey Sharybin (sergey)
Differential Revision: https://developer.blender.org/D13741
2022-01-06 12:08:16 +01:00
e53bf0655e Add custom icons for Blender ID badges
Reviewed By: Sergey Sharybin (sergey)
Differential Revision: https://developer.blender.org/D13535
2021-12-10 13:00:51 +01:00
114b2b9f1b Disable as Spam - admin-only option to handle spammers
This implements a convenient way to handle spam users, mimicking the steps the
admins already do manually:

Performed tasks:
* Disable Account
* Real Name -> "spam"
* Title -> ""
* Icon → ""
* Blurb → ""
* Profile Picture → default

This is disabling the account using the same functionality as the "Disable User"
button. And on top of that it replaces the account real name with "spam" and
wipe the other personal information.

Note that this doesn't delete any posts from the user.
2021-07-14 16:21:22 +02:00
883046f69f Phabricator: Extend CONDUIT API to support xaction transactions
Example of null transactions and their expected value:

* D9:transaction 44 - "abandon"
* D22:transaction 112 - "commit"
* D22:transaction 96 - "accept"
* D1123:transaction 22872 - "rethink"

Reviewers: sergey

https://developer.blender.org/D11568
2021-06-10 16:34:13 +02:00
1810f1dda9 Fix duplicate system users added on every update
Was a mistake in the way how key de-duplication and non-phabricator-managed
commit access was dealt with.

For now simple fix: make sure user list is unique. Proper fix would require
something more sophisticated.
2021-03-05 12:10:25 +01:00
14aa6a671b Merge branch 'master' into blender-tweaks 2020-11-18 14:29:07 +01:00
epriestley
34082efb02 Add a basic "harbormaster.step.edit" API method
Summary: Ref T13585. Provide a minimal but technically functional "harbormaster.step.edit" API method.

Test Plan: Used the web console to modify the URI for a "Make HTTP Request" build step.

Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam

Maniphest Tasks: T13585

Differential Revision: https://secure.phabricator.com/D21489
2020-11-03 12:50:18 -08:00
epriestley
bf8707d3a9 Add a basic "harbormaster.step.search" API method
Summary: Ref T13585. This isn't particularly useful (notably, it does not include custom field values and isn't searchable by build plan PHID) but get the basics into place.

Test Plan: Used the web UI to make API calls, reviewed results.

Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam

Maniphest Tasks: T13585

Differential Revision: https://secure.phabricator.com/D21488
2020-11-03 12:50:17 -08:00
epriestley
ae5a38f334 Guarantee terms in PhabricatorAuthPasswordEngine are strings
Summary:
Ref T2312. Numeric strings are read out of arrays as integers, and modern PHP raises appropriate warnings when they're then treated as strings.

For now, cast the keys to strings explicitly (we know we inserted only strings). In the future, introduction of a `StringMap` type or similar might be appropriate.

Test Plan:
  - Added "abc.12345.xyz" to the blocklist, changed my VCS password.
  - Before: fatal when trying to "strpos()" an integer.
  - After: password change worked correctly.

Maniphest Tasks: T2312

Differential Revision: https://secure.phabricator.com/D21487
2020-11-03 11:04:49 -08:00
epriestley
c04147328f Fix isValidGitShallowCloneResponse
Summary:
Changes the heuristic method by which non-zero exit statuses from git-http-backend are found to be due to packfile negotiation during shallow fetches, etc.

Instead of checking git-http-backend stderr for a generic "hung up" error message, see if the pack-result response contains a terminating flush packet ("0000"). This should give a greater assurance that the request was handled correctly and the response is complete.

Test Plan: Run `GIT_CURL_VERBOSE=1 git fetch --depth 1 https://host.example/source/repo.git HEAD` to ensure it completes and includes two successful POST requests during packfile negotiation (the last one actually receives the packfile).

Reviewers: epriestley, #blessed_reviewers

Reviewed By: epriestley, #blessed_reviewers

Subscribers: Korvin, dzduvall

Tags: #diffusion

Differential Revision: https://secure.phabricator.com/D21484
2020-10-30 13:46:24 -07:00
epriestley
671986592b Add a missing "GROUP BY" to MailQuery when querying for multiple recipients
Summary:
See <https://discourse.phabricator-community.org/t/mail-details-view-broken/4315>. The change in D21400 detects a missing "GROUP BY" in some variations of this query.

Specifically, we may join multiple recipient rows (since mail may have multiple recipients) and then fail to group the results.

Fix this by adding the "GROUP BY". Additionally, remove the special-cased behavior when no authors or recipients are specified -- it's complicated and not entirely correct (e.g., may produce a "no object" instead of a policy error when querying by ID), and likely predates overheating.

Test Plan:
  - Disabled `metamta.one-mail-per-recipient` in Config.
  - Generated a message to 2+ recipients.
  - Viewed the message detail; queried for the message by specifying 2+ recipients.
  - Viewed the unfiltered list of messages, saw the query overheat.

Differential Revision: https://secure.phabricator.com/D21486
2020-10-30 13:02:22 -07:00
epriestley
bc4f86d279 When a new, deleted, draft inline is revived with "Undo", undelete it
Summary:
See PHI1876. Normally, deleted inlines are undeleted with an "undelete" operation, which clears the "isDeleted" flag.

However, when an inline is deleted implicitly by using "Cancel" without first saving it, the flag currently isn't cleared properly. This can lead to cases where inlines seem to vanish (they are shown to the user in the UI, but treated as deleted on submission).

Test Plan:
There are two affected sequences here:

  - Create a new inline, type text, cancel, undo.
  - Create a new inline, type text, cancel, undo, save.

The former sequence triggers an "edit" operation. The subsequent "Save" in the second sequence triggers a "save" operation.

It's normally impossible in the UI to execute a "save" without executing an "edit" first, but "save" clearly should undelete the comment if you get there somehow, so this change clears the deleted flag in both cases for completeness.

  - Executed both sequences, saw comment persist in preview, on reload, and after submission.

Differential Revision: https://secure.phabricator.com/D21483
2020-10-19 12:34:03 -07:00
epriestley
b2e96df3a3 Update "arc call-conduit" instructions in Conduit API console for required "--"
Summary: See PHI1912. Ref T13491. "arc" now requires "--" when stdin is not a TTY; provide this argument for users.

Test Plan: Viewed example in console, saw "--". Executed example.

Maniphest Tasks: T13491

Differential Revision: https://secure.phabricator.com/D21482
2020-10-19 12:02:30 -07:00
epriestley
2b8bbae5fb Set an explicit height when drawing the dependent revision graph
Summary:
See PHI1900. Recent changes to how commit graphs are drawn made the height automatic in most cases, but it fails in Differential because the element isn't initially visible so the computed height is 0.

Just give them an explicit height so they show up again.

Test Plan: Viewed graphs in Maniphest, Differential, and Diffusion; saw them all render properly.

Differential Revision: https://secure.phabricator.com/D21481
2020-10-16 14:10:36 -07:00
epriestley
058d2489e7 Expose the "file attached to object" and "object attached to file" edges via "edge.search"
Summary:
See PHI1901. An install would like improved support for identifying files related to an object (like a task or revision) for retention/archival/backup/migration/snapshotting purposes.

The "attachment" edge is not really user-level: it just means "if you can see the object, that allows you to see the file". This set includes files that users may not think of as "attached", like thumbnails and internal objects which are attached for technical reasons.

However, this is generally an appropriate relationship to expose for retention purposes.

Test Plan: Used "edge.search" to find files attached to a revision and objects attached to a file.

Differential Revision: https://secure.phabricator.com/D21480
2020-10-16 13:45:35 -07:00
epriestley
1f7c736f9a Add a "Comment content" field to Herald
Summary: Ref T13583. To improve support for making it harder to improperly mix data retention policies, allow Herald to act on comment content.

Test Plan:
  - Wrote comment content Herald rules in Maniphest and Differential.
  - Submitted non-matching comments (no action) and matching comments (Herald action).
  - In Differential, triggered rules by submitting non-matching main content and a matching inline comment.

Maniphest Tasks: T13583

Differential Revision: https://secure.phabricator.com/D21479
2020-10-16 13:42:56 -07:00
epriestley
0f27cd46cc Never render "Show More Context" inside an inline comment suggestion diff
Summary:
See PHI1896. If you do this:

  - Create an inline comment over a wide range of lines.
  - Suggest an edit.
  - Make a change near the beginning of the block.
  - Make a change near the end of the block.
  - Save the inline.

...you get a rendering which includes a "Show More Context" fold in the middle.

Currently, this element renders in a visually broken way and consumes too many columns.

However, this element isn't ever desirable inside inline comment suggestions. Stop it from rendering entirely.

Test Plan:
  - Made an inline comment suggestion across lines 1-50 with edits at the beginning and end, saw a contiguous diff.
  - Made smaller inline comment suggestions (one line, a few lines).

Differential Revision: https://secure.phabricator.com/D21476
2020-10-02 09:47:32 -07:00
epriestley
0f0e94ca71 Use "getInlines()", not "_inlines", to access inlines on client Changeset objects
Summary:
See PHI1898. An install is reporting an execution/initialization order issue where this code is reachable before `_inlines` is initialized.

I can't immediately reproduce it, but using "getInlines()" is preferable anyway and seems likely to fix the problem.

Test Plan: Viewed revisions with inlines, added/removed/edited/replied to inlines, didn't find anything broken.

Differential Revision: https://secure.phabricator.com/D21475
2020-10-02 09:19:04 -07:00
epriestley
a5f20f7106 When printing, wrap all content in Remarkup tables more aggressively
Summary:
Ref T13564. See PHI1798. Earlier efforts here (see D21439) still leave us with:

  - Incorrect behavior for long URIs, like `http://www.example.com/MMMMM...`.
  - Incorrect beahvior for long text blocks, like `MMMMMM...`.
  - Undesirable behavior for monospaced text in non-printing contexts (it wraps when we'd prefer it not wrap).

Apply the wrapping rules to all "<td>" content to resolve these three prongs.

Test Plan:
  - Viewed long URIs, text blocks, and monospaced text in and out of tables, while printed and not printed, in Safari, Firefox, and Chrome.
  - All browser behavior now appears to be correct ("all content is preserved in printed document").
  - Some browser behavior when making wrapping choices is questionable, but I can't find an automatic solution for that.

Maniphest Tasks: T13564

Differential Revision: https://secure.phabricator.com/D21472
2020-09-28 09:47:46 -07:00
epriestley
58d3f6145a Fix an issue where known Subversion commits are incorrectly shown as "Discovering..."
Summary:
Ref T13552. The behavior of "RepositoryQuery" with ambiguous identifiers under "withRepositoryPHIDs()" is tricky. This leads to failure to load commits in Subversion in some cases.

Use "withRepository()", which gives us the correct identifier resolution behavior.

Test Plan: Viewed a subversion repository history in Diffusion, saw commit details after change.

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21469
2020-09-17 13:55:47 -07:00
epriestley
f21a00a315 Fix an out-of-order issue in the new update-during-publish behavior
Summary:
Ref T13552. The Herald field "Accepted Differential revision" (and similar fields) depend on the task/revision update steps running before Herald executes.

Herald currently executes first, so it never sees associated revisions. Swap this order.

Test Plan: Published a commit, got a clean parse/import. Will test with production rules ("Cowboy Commits").

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21468
2020-09-17 13:40:45 -07:00
epriestley
a754c694de Add missing indexes to DrydockRepositoryOperation
Summary: See PHI1885. Repository operations are queryable by state and author, but neither column has a usable key. Add usable keys.

Test Plan: Ran EXPLAIN on a state query. Ran `bin/storage upgrade`. Ran EXPLAIN again, saw query go from a table scan to a `const` key lookup.

Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam

Differential Revision: https://secure.phabricator.com/D21465
2020-09-17 12:10:00 -07:00
epriestley
969587f7b0 Log unexpected exceptions raised by Conduit calls
Summary:
Ref T13581. Currently, unexpected exceptions inside Conduit calls are passed to the client, but not logged on the server.

These exceptions should generally be unexpected, and producing a server-side trace is potentially useful.

Test Plan: Simulated a during-execution exception, saw it get logged on the server.

Maniphest Tasks: T13581

Differential Revision: https://secure.phabricator.com/D21464
2020-09-15 17:36:43 -07:00
epriestley
2a83df5786 Fix an issue where a GROUP BY was missing when a query matched a revision using multiple hashes
Summary:
Ref T13581. If you query for revisions by hash and provide multiple hashes (A, B) which match a single revision (e.g., older and newer diffs for that revision), the query omits a GROUP BY clause but should contain one.

Add a GROUP BY clause in this case.

Test Plan:
With a working copy that has multiple hashes corresponding to a single revision, ran `arc branches` before and after the change. Before, got this error:

```
[2020-09-15 17:02:07] EXCEPTION: (ConduitClientException) ERR-CONDUIT-CORE: Rows passed to "loadAllFromArray(...)" include two or more rows with the same ID ("130"). Rows must have unique IDs. An underlying query may be missing a GROUP BY. at [<arcanist>/src/conduit/ConduitFuture.php:65]
```

After, clean execution.

Maniphest Tasks: T13581

Differential Revision: https://secure.phabricator.com/D21462
2020-09-15 17:36:42 -07:00
epriestley
6f78e2a91c When a commit is marked "closeable", clear the "published" flag
Summary:
Ref T13552. When a previously discovered commit becomes reachable from a permanent ref, we re-queue workers to update it. However, the commit may already be marked as "published", so the publish worker may do nothing.

It would perhaps be simpler to not mark the commit as published when it isn't reachable from a permanent ref, but this is tricky because the flag is also part of the "imported / all steps" state (see T13580).

Until that can be cleaned up, just clear the flag.

Test Plan:
  - Pushed a commit with "fixes X" to a non-permanent branch.
  - Pushed it to a permanent branch.
  - Before change: task failed to close.
  - After change: task closes properly.

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21460
2020-09-15 17:36:42 -07:00
epriestley
737e7c8541 When an in-process worker subtask fails permanently, don't fatal the whole process
Summary:
Ref T13552. Fixes T13569. Currently, if a process uses in-process tasks (usually, a debugging/diagnostic workflow) and those tasks (or tasks those tasks queue) fail permanently, the exception escapes to top level and the process exits.

This isn't desirable; catch the exception and fail them locally instead.

Test Plan:
With a failing Asana integration and misconfigured Webhook, ran `bin/repository reparse --publish ...`.

  - Before: fatals on each substep.
  - After: warnings emitted for failed substep, but process completes.

Maniphest Tasks: T13569, T13552

Differential Revision: https://secure.phabricator.com/D21459
2020-09-15 17:36:41 -07:00
epriestley
93ef902ffa Fix a view fatal in CommitGraphView when commits are undiscovered
Summary:
Ref T13552. See <https://discourse.phabricator-community.org/t/viewing-repository-history-for-svn-repository-causes-unhandled-exception/4225/>.

This condition is flipped and can fatal by passing a `NULL` value for `$commit` to a typehinted method.

Test Plan: Viewed history page with undiscovered commits.

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21458
2020-09-15 17:36:41 -07:00
epriestley
a39c590442 Move task and revision closure to the "publishing" step of the commit import pipeline
Summary:
Ref T13552. Now that these steps can build their own "CommitRef" object from storage on the "CommitData" object, move them from the "Message" step to the "Publishing" step.

This should resolve the root issue in T13552, where a commit moved from a non-permanent branch to a permanent branch does not publish closures properly.

Test Plan: Used "bin/repository reparse --publish ..." to republish changes.

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21450
2020-09-15 17:36:40 -07:00
epriestley
cebde34425 Make "CommitData" wrap and persist a "CommitRef" record
Summary:
Ref T13552. Turn "CommitData" into an application-level layer on top of the repository-level "CommitRef" object.

For older commits which will not have a "CommitRef" record on disk, build a synthetic one at runtime. This could eventually be migrated.

Test Plan: Ran "bin/repository reparse --message", browsed Diffusion.

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21449
2020-09-15 17:36:40 -07:00
epriestley
e454c3dafe Wrap all direct access to author/committer properties on "CommitData"
Summary: Ref T13552. Currently, various callers read raw properties off "CommitData" directly. Wrap these in accessors to support storage changes which persist "CommitRef" information instead.

Test Plan:
- Ran "diffusion.querycommits", saw the same data before and after.
- Looked at a commit, saw authorship information and date.
- Viewed tags in a repository, saw author information.
- Ran "rebuild-identities", saw no net effect.
- Grepped for callers to "getCommitDetail(...)".

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21448
2020-09-15 17:36:39 -07:00
epriestley
7d6874d9f0 Turn "bypassCache" into a no-op in "diffusion.querycommits"
Summary: Ref T13552. The internal caller for this now uses "internal.commit.search", which is always authority-reading. No legitimate external caller should rely on the behavior of "bypassCache"; no-op it to simplify behavior.

Test Plan: Called "diffusion.querycommits", saw the same data as before.

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21447
2020-09-15 17:36:39 -07:00
epriestley
3a80efa440 Build "DiffusionCommitRef" objects from "internal.commit.search", not "diffusion.querycommits", in the message parser worker
Summary: Ref T13552. Swap the call we're using to build "CommitRef" objects here to the recently-introduced "internal.commit.search" method.

Test Plan: Used "bin/repository reparse --message ..." to reparse commits, added "var_dump()" to inspect results. Saw sensible CommitRef and CommitData objects get built.

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21446
2020-09-15 17:36:39 -07:00
epriestley
f6238f9d9b Remove "bin/repository lookup-users" workflow
Summary:
Ref T13552. This is one of two callsites to "diffusion.querycommits". It's an old debugging workflow which I haven't used in years and which is likely obsoleted by identities and other changes.

I believe the root problem here was also ultimately user error (a user has misconfigured their local Git author email as another user).

Test Plan: Grepped for "lookup-users", got no hits.

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21444
2020-09-15 17:36:38 -07:00
epriestley
a9506097ea Add "internal.commit.search" to replace the cache bypass mode of "diffusion.querycommits"
Summary:
Ref T13552. Commit parsers currently invoke a special mode of "diffusion.querycommits", which is an older frozen method.

The replacement, "diffusion.commit.search", is not really appropriate for low-level access. This mode of having a single method which operates in "cache" or "non-cache" modes also ends up in a lot of unnecessary field shuffling.

Provide "internal.commit.search" as a modern equivalent that returns a "DiffusionCommitRef"-compatible structure.

Test Plan: Executed "internal.commit.search", got sensible low-level commit results.

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21443
2020-09-15 17:36:38 -07:00
epriestley
a745055813 Lift Diffusion Conduit call proxying to the root level of Conduit
Summary:
Ref T13552. Some Diffusion conduit calls may only be served by a node which hosts a working copy on disk, so they're proxied if received by a different node.

This capability is currently bound tightly to "DiffusionRequest", which is a bundle of context parameters used by some Diffusion calls. However, call proxying is not fundamentally a Diffusion behavior.

I want to perform proxying on a "*.search" call which does not use the "DiffusionRequest" parameter bundle. Lift proxying to the root level of Conduit.

Test Plan: Browsed diffusion in a clusterized repsository.

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21442
2020-09-15 17:36:37 -07:00
epriestley
367cd28927 Delete some commit dead parsing code
Summary: Ref T13552. Neither "$hashes" or "$user" are used, and constructing them has no side effects.

Test Plan: Searched for these symbols.

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21441
2020-09-15 17:36:37 -07:00
epriestley
3dfa89dd5d Update SES API to use AWSv4 signatures
Summary:
Ref T13570. Fixes T13235. In most cases, we use modern (v4) signatures for almost all AWS API calls, and have for several years.

However, sending email via SES currently uses an older piece of external code which uses the older (v3) signature method.

AWS is retiring v3 signatures on October 1 2020, so this pathway will stop working.

Update the pathway to use `PhutilAWSFuture`, which provides v4 signatures.

T13235 discusses poor error messages from SES. Switching to Futures fixes this for free, as they have more useful error handling.

Test Plan:
  - Configured an SES mailer, including the new `region` parameter.
  - Used `bin/mail send-test` to send mail via SES.
  - Sent invalid mail (from an unverified address); got a more useful error message.
  - Grepped for removed external, no hits.

Maniphest Tasks: T13570, T13235

Differential Revision: https://secure.phabricator.com/D21461
2020-09-15 13:03:49 -07:00
epriestley
6e1b5da112 Fix additional "xprintf()"-class static parameter lint errors
Summary: Ref T13577. After the fix in D21453, lint identifies additional static errors in Phabricator; fix them.

Test Plan: Ran `arc lint`; these messages are essentially all very obscure.

Subscribers: hach-que, yelirekim, PHID-OPKG-gm6ozazyms6q6i22gyam

Maniphest Tasks: T13577

Differential Revision: https://secure.phabricator.com/D21457
2020-09-08 11:45:48 -07:00
epriestley
7daaaa8463 Remove obsolete write to "pid" property in "annihilateProcessGroup()" in Daemon Overseer
Summary: Ref T13579. This property was removed in D21425, but I missed this usage site. Remove the assignment; this class no longer tracks the subprocess PID directly.

Test Plan: Searched for "->pid", no further hits.

Maniphest Tasks: T13579

Differential Revision: https://secure.phabricator.com/D21452
2020-09-04 16:41:36 -07:00
epriestley
0854425d19 When printing timestamps on paper: use an absolute, context-free date format
Summary:
Ref T13573. Using the browser "Print" feature on pages produces "Thu, Aug 4, 12:22" timestamps which require context to interpret precisely (they don't have a year and don't have a timezone).

Instead, retain these timestamps in "screen" contexts but use "YYYY-MM-DD HH:MM:SS (UTC+X)" timestamps when printing.

Test Plan: Printed Maniphest tasks and other pages in Safari and Chrome using "?__print__=1" and "Print to PDF", saw absolute timestamps after this chagne in the printed documents.

Maniphest Tasks: T13573

Differential Revision: https://secure.phabricator.com/D21451
2020-09-04 16:36:34 -07:00
epriestley
72f149bf39 Require rows passed to "loadAllFromArray()" have unique keys
Summary:
See PHI1809, which identified a bug in Project search where queries with a large number of slugs could paginate improperly.

This change detects problems in this category: cases where multiple rows with the same ID are passed to "loadAllFromArray()". It's likely that all cases it detects are cases where a GROUP BY is missing.

Since this might have some false positives or detect some things which aren't fundamentally problematic, I'm planning to hold it until the next release.

Test Plan:
  - Reverted D21399, then created a project with multiple slugs and queried for one of them via "project.search". Hit this new exeception.
  - Browsed around a bit, didn't immediately catch any collateral damage.

Differential Revision: https://secure.phabricator.com/D21400
2020-08-12 09:05:14 -07:00
epriestley
429543b637 Fix some content/background overflow issues with commit graph lists
Summary: Ref T13552. There are currently some content overflow issues on the graph view where the menu height can exceed the content height and the frame is drawn on a sub-element. Make the frame draw around all the content.

Test Plan: Viewed commit graph history view, saw more sensible UI.

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21440
2020-08-12 09:04:09 -07:00
epriestley
0b64092d25 Improve handle/status list display on devices in commit graph lists
Summary: Ref T13552. Provide a richer handle/status list item for commit lists.

Test Plan: Viewed commits in various interfaces, saw richer information.

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21431
2020-08-12 09:04:08 -07:00
epriestley
49af92e903 Improve commit action item layout on mobile
Summary:
Ref T13552. Build the "commit list" elements so that the menu action items collapse under the element on mobile.

Also change the mobile breakpoint to 512px because my Safari window can't go any narrower than 508px. Future changes to responsive design will be more content-aware anyway.

Test Plan: Looked at commits in various interfaces, at desktop and mobile widths.

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21430
2020-08-12 09:04:07 -07:00
epriestley
57f9450bcf Improve desktop and mobile layouts for new "CommitGridView"
Summary:
Ref T13552. The current layout doesn't work particularly well on desktops or devices.

We have some device/desktop table layout code, but it isn't generic. We also have property list layout code, but it isn't generic either.

Provide generic layout elements ("Fuel", from "Phabricator UI Layout" to "PHUIL"?) and narrowly specialize their display behavior. Then swap the ListItemView stuff to use it.

Test Plan:
Saw slightly better responsive behavior:

{F7637457}

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21418
2020-08-12 09:04:07 -07:00
epriestley
8aec3f916b Unify more build, property, auditor, and status information into "CommitGraphView"
Summary:
Ref T13552. In unifying the various Graph/List/Table commit views, some information was dropped -- particularly, audit status.

Restore most of it. The result isn't very pretty, but has most of the required information.

Test Plan: {F7637411}

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21417
2020-08-12 09:04:06 -07:00
epriestley
36dac46ff2 Clean up some minor commit list CSS
Summary: Ref T13552. Some of the CSS can be removed or simplified now that essentially all lists of commits are on a single rendering pathway.

Test Plan: Grepped for affected CSS, viewed commit graph.

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21416
2020-08-12 09:00:09 -07:00
epriestley
57ee6649aa Remove "PhabricatorAuditListView"
Summary: Ref T13552. Remove yet another way to render a list of commits, and unify it with "CommitGraphView".

Test Plan:
  - Viewed commit search results.
  - Viewed owners package detail page.

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21415
2020-08-12 09:00:02 -07:00
epriestley
2b0632b442 Remove "DiffusionHistoryTableView" and "DiffusionHistoryView"
Summary:
Ref T13552.

Currently, the "Browse" page shows a snippet of unmerged changes if you're looking at a non-default branch. Remove this for consistency with the simplified main "Browse" page. This is reachable via "Compare".

Update the "Compare" page to use the new "CommitGraphView".

Test Plan:
  - Looked at the "Browse" page of "stable".
  - Looked at the "Compare" page for "stable vs master".

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21414
2020-08-12 08:59:53 -07:00
epriestley
7087c0439a Move the view of merged changes to "DiffusionCommitGraphView"
Summary: Ref T13552. When viewing a merge commit, merged changes are currently shown inline. Update this view to use the new "GraphView" rendering pipeline.

Test Plan:
  - Viewed a merge commit, saw merges.
  - Viewed history, profile page, etc.

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21413
2020-08-12 08:59:46 -07:00
epriestley
cd09ba5e19 Replace "DiffusionCommitListView" with "DiffusionCommitGraphView"
Summary:
Ref T13552. This older view mostly duplicates other code and has only two callsites:

  - The "Commits" section of user profile pages.
  - The "Ambiguous Hash" page when you visit a commit hash page which is an ambiguous prefix of two or more commit hashes.

Replace both with "DiffusionCommitGraphView".

Test Plan:
  - Visited profile page, clicked "Commits".
  - Visited an ambiguous hash page (`rPbd3c23`).

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21412
2020-08-12 08:59:39 -07:00
epriestley
9fa2525384 Improve rendering of history graph in "CommitGraphView"
Summary: Ref T13552. In the new combined "table/list" graph view, tidy up the graph rendering.

Test Plan: {F7633504}

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21411
2020-08-12 08:59:31 -07:00
epriestley
46695c76eb Introduce "DiffusionCommitGraphView", which unifies "HistoryListView" and "HistoryTableView"
Summary:
Ref T13552. Currently, commit lists are sometimes rendered as an object list and sometimes rendered as a table. There are two separate views for table rendering.

Add a fourth view ("list, with a graph") with the eventual intent of unifying all the other views. For now, this only replaces "HistoryListView" -- and needs some more work to really be a convincing replacement.

Test Plan:
  - Looked at "History" in Diffusion, saw an ugly view with all the information we want.
  - Grepped for "HistoryListView", no hits.

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21410
2020-08-12 08:59:23 -07:00
epriestley
c6de7c66a3 Remove the "Graph" view as a dedicated repository view
Summary:
Ref T13552. Currently, Diffusion has two effectively identical history views, the "Graph" view and the "History" view.

These arose out of product uncertainty about the importance of the graph, but I think we can just put the graph on the "object item list" view and merge these views.

Test Plan: Looked at repositories in Diffusion, no longer saw a "Graph" tab. Grepped for "graph"-related symbols.

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21409
2020-08-12 08:59:16 -07:00
epriestley
9afc5c6287 Remove "Recent Commits" from repository landing page
Summary:
Ref T13552. Currently, the repository landing page has a panel with recent commits. This is accessible by clicking "History" and usually below the fold, so it's not clearly useful.

Since I'm consolidating this code anyway to fix an issue with the import pipeline, just get rid of this history view.

Test Plan: Viewed a repository landing page, no longer saw a history panel.

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21408
2020-08-12 08:59:07 -07:00
epriestley
c8a279957d Remove "DiffusionTagTableView"
Summary: Ref T13552. This older class has no callers; tag and branch listings were replaced with an "ObjectList" view.

Test Plan: Grepped for "DiffusionTagTableView", got no hits.

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21407
2020-08-12 08:58:59 -07:00
epriestley
60e9f64190 Remove the "authored" subheader from commits
Summary:
Ref T13552. I'm trying to reduce the number of direct callers to commit authorship metadata. This header seems low-value enough to simply remove; this information is shown more clearly and prominently in the "Provenance" UI.

In particular, commits have multiple dates (authored, committed, pushed) but this header shows only one. It currently shows the author identity and the commit date, which isn't entirely correct. And it potentially uses an "Identity" as a timeline actor, which is conceptually fine but not entirely firm ground.

Test Plan: Viewed a commit, saw no more subheader.

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21406
2020-08-12 08:58:51 -07:00
epriestley
7fd6bf26a9 Modernize "Author" and "Committer" rendering for commits
Summary:
Ref T13552. Give "Commit" objects a more modern, identity-aware way to render author and committer information.

This uses handles in a more modern way and gives us a single read callsite for raw author and committer names.

Test Plan:
  - Grepped for callers to the old methods, found none. (There are a lot of "renderAuthor()" callers in transactions, but this call takes no parameters.)
  - Viewed some commits, saw sensible lists of authors and committers.

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21405
2020-08-12 08:58:43 -07:00
epriestley
81e4e5b7f9 Remove construction of "author" information from "LastModified" payload in Diffusion
Summary:
Ref T13552. When viewing a directory in Diffusion, we make an Ajax call to get the last commit for each path.

This call currently pulls author information, since an older version of this UI showed author information.

The current UI does not show author information, so this parameter is unused. Delete the code which builds it.

Test Plan: Grepped for `'author'` and references to the "pull-lastmodified" behavior. This behavior is invoked in only one place, which never generates an author placeholder.

Maniphest Tasks: T13552

Differential Revision: https://secure.phabricator.com/D21404
2020-08-12 08:58:32 -07:00
epriestley
5454175973 Coerce Chrome into breaking monospaced text when printing tables to PDFs
Summary:
See T13564. In Chrome only, printing tables with a cell containing an unbroken monospaced text element fails to wrap/break the cell.

Adding "overflow-wrap" appears to fix this without making anything worse. Try this until new problems arise.

Test Plan: Printed such a table to PDF in Chrome, got wrapping with all content visible in the PDF.

Differential Revision: https://secure.phabricator.com/D21439
2020-08-12 07:34:54 -07:00
epriestley
79375c6c53 Make "Quote" work properly in Pholio
Summary:
See <https://discourse.phabricator-community.org/t/quote-comment-missing-on-mock-pages/4155>. Pholio is currently missing a couple of configuration calls to make the "Quote" action work.

Moving to EditEngine is the "real" fix, but this fix is trivial and should make "Quote" work properly with no negative effects.

Test Plan: Viewed a mock, used "quote" to quote a comment.

Differential Revision: https://secure.phabricator.com/D21437
2020-08-10 13:40:25 -07:00
epriestley
ce0dc9a2ba Correct an apparent off-by-one error when adjusting inlines across revision changes
Summary:
See PHI1834. It's not obvious why this "+1" is present in the code, but it causes inlines to be adjusted incorrectly when a file is not modified across changes. See D21435.

Remove it, which appears to produce accurate adjustment behavior.

Test Plan:
  - See D21435 for instructions to build a change, where a file with lines "A-Z" is unmodified across Diff 1 and Diff 2.
  - Left inlines on lines 14, 17-19, and 16-26 (end of the file) on Diff 1.
  - Before: saw inlines incorrectly adjusted to lines 15, 18, and 17 on Diff 2. Before D21435, the last inline was culled by the rendering engine.
  - After: saw inlines correctly adjusted to lines 14, 17, and 16 (the same lines as the original), render properly, and highlight the correct lines when hovered.

Differential Revision: https://secure.phabricator.com/D21436
2020-08-05 13:12:53 -07:00
epriestley
dbdfac1e07 Recover inline comments which are "adjusted" off the end of a diff
Summary:
See PHI1834. Currently, the inline adjustment engine can sometime "adjust" an inline off the end of a diff. If it does, we lay it out on an invalid display line here and never render it.

Instead, make sure that layout never puts a comment on an invalid line, so the UI is robust against questionable decisions by the adjustment engine: no adjustment should be able to accidentally discard an inline.

Test Plan:
  - Created a two diff revision, where Diffs 1 and 2 have "alphabet.txt" with A-Z on one line each. The file is unchanged across diffs; some other file is changed.
  - Added a comment to lines P-Z of Diff 1.
  - Before: comment is adjusted out of range on Diff 2 and not shown in the UI.
  - After: comment is still adjusted out of range internally, but now corrected into the display range and shown.

Differential Revision: https://secure.phabricator.com/D21435
2020-08-05 13:12:52 -07:00
epriestley
2db1955159 In Jupyter notebooks, read strings stored in the raw as either "string" or "list<string>" more consistently
Summary:
Ref PHI1835. Generally, Jupyter notebooks in the wild may store source and markdown content as either a single string or a list of strings.

Make the renderer read these formats more consistently. In particular, this fixes rendering of code blocks stored as a single string.

This also fixes an issue where cell labels were double-rendered in diff views.

Test Plan:
Created a notebook with a code block represented on disk as a single string, rendered a diff from it.

{F7696071}

Differential Revision: https://secure.phabricator.com/D21434
2020-08-05 12:26:00 -07:00
epriestley
98e0440d45 In 1-up source diffs, retain the "No newline at end of file" on "\" lines
Summary:
See PHI1839. Currently, the "No newline at end of file" text is dropped in the 1-up diff view for changes that affect a file with no trailing newline.

Track it through the construction of diff primitivies more carefully.

Test Plan: {F7695760}

Differential Revision: https://secure.phabricator.com/D21433
2020-08-05 10:16:58 -07:00
epriestley
c1eeacd850 Move "Wait for Previous Commits to Build" out of prototype
Summary:
Although I'm not entirely thrilled about doing flow control like this (as an actual action in a build plan), I believe this build step works correctly and there's no fancy replacement mechanism on the immediate horizon, and this didn't send us down a slippery slope of Turing-complete builds encoded without real structure or context. Just kick it out of prototype.

(Other approaches which might be better in the long run are things like "this is a top-level behavior on the build plan itself" and/or "build plans are written in a DSL, not a Javascript UI".)

Test Plan: Added a new build step, saw this as an option in the "Flow Control" section.

Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam

Differential Revision: https://secure.phabricator.com/D21432
2020-07-30 12:46:47 -07:00
epriestley
017ef1927c Revert use of "user-select: all" to modify tab selection behavior
Summary:
Reverts D21419. See PHI1814. Previously, I used "user-select: all" to group sequences of spaces for selection.

However, this has a side effect: the sequence is now selected with a single click. I didn't read the docuementation on the CSS property thoroughly and missed this in testing, since I was focused on drag-selection behavior.

This behavior is enough of a net negative that I think we're in a worse state overall; revert it.

Test Plan: Straight revert.

Differential Revision: https://secure.phabricator.com/D21429
2020-07-24 13:41:26 -07:00
epriestley
a27c83757d Remove ancient "phd.trace" and "phd.verbose" configuration options
Summary:
Ref T13556. These options are very old and effectively obsoleted by "bin/phd debug [--trace]". I haven't used either option diagnostically in many years, and they aren't mentioned in the documentation.

Remove them to simplify configuration, and because "phd.trace" doesn't work anyway and likely hasn't for a long time -- it has specific issues with TTY detection (see T13556).

Test Plan: Grepped for "phd.trace" and "phd.verbose". Ran "bin/phd debug [--trace]" and saw verbose/trace output.

Maniphest Tasks: T13556

Differential Revision: https://secure.phabricator.com/D21426
2020-07-23 12:31:32 -07:00
epriestley
78d1b62bb8 Streamline handling of Futures and PIDs in daemons
Summary:
Ref T13555. Currently, the daemon future may resolve into a failure state immediately inside "start()", and not have a valid PID when we read it.

Instead, read PIDs from the current active future in all cases, using "hasPID()" to test for the presence of a valid PID.

Since we don't query the PID immediately, we no longer need to explicitly start the future.

Also fix an issue where the same future could be added to the overseer pool more than once if it threw on "resolve()". In general:

  - Before we "resolve()" a future, detach it from the DaemonHandle: we're always done with it.
  - Catch exceptions on resolution and treat them the same way as subprocess resolution errors. These aren't common, but are possible in the general case.
  - Have DaemonHandle add futures to the future pool directly when they're created.

Test Plan:
  - Ran daemons with intentional subprocess creation failures, saw clean recovery.
  - Ran daemons with intentional resolution exceptions, saw clean recovery.

Maniphest Tasks: T13555

Differential Revision: https://secure.phabricator.com/D21425
2020-07-23 11:22:31 -07:00
epriestley
5f0535934d Manage PIDs more carefully in DaemonHandle
Summary:
Ref T13555. Although these callsites may not actually impact anything, it's possible for an active handle to have no PID (e.g., if the subprocess failed to start).

Handle these cases more carefully.

Test Plan: Started daemons, saw them run fine. See also next change.

Maniphest Tasks: T13555

Differential Revision: https://secure.phabricator.com/D21424
2020-07-23 11:22:30 -07:00
epriestley
fcb75d0503 Fix an issue where prose diffing may fail after hitting the PCRE backtracking limit
Summary:
Fixes T13554. For certain prose diff inputs and PCRE backtracking limits, this regular expression may back track too often and fail.

A characteristic input is "x x x x ...", i.e. many sequences where `(.*?)\s*\z` looks like it may be able to match but actually can not.

I think writing an expression which has all the behavior we'd like without this backtracking issue isn't trivial (at least, I don't think I know how to do it offhand); just use a strategy based on "trim()" insetad, which avoids any PCRE complexities here.

Test Plan: Locally, this passes the "x x x ..." test which the previous code failed. I'm not including that test because it won't reproduce across values of "pcre.backtrac_limit", PCRE versions, etc.

Maniphest Tasks: T13554

Differential Revision: https://secure.phabricator.com/D21422
2020-07-23 07:46:15 -07:00
epriestley
8f9ba48528 Fix an issue with destruction of Revision and Diff objects with viewstates
Summary:
See <https://discourse.phabricator-community.org/t/domainexception-when-trying-to-remove-an-differentialrevision/4105>.

These queries aren't actually constructed properly, and destroying a revision or diff with viewstates currently fails.

Test Plan: Used `bin/remove destroy Dxxx` to destroy a revision with viewstates (this also destroys the associated diffs).

Differential Revision: https://secure.phabricator.com/D21421
2020-07-22 13:07:11 -07:00
epriestley
0ed5569e9f Likely, fix a warning when rendering modified coverage
Summary: See PHI1819. This structure may have `null` elements.

Test Plan: Will confirm user reproduction case.

Differential Revision: https://secure.phabricator.com/D21420
2020-07-17 19:45:26 -07:00
epriestley
37ffb71c4d In source views, wrap display tabs in "user-select: all" to improve cursor selection behavior
Summary:
Ref T2495. See PHI1814. Currently, Phabricator replaces tabs with spaces when rendering diffs.

This may or may not be the best behavior in the long term, but it gives us more control over expansion of tabs than using tab literals.

However, one downside is that you can use your mouse cursor to select "half a tab", and can't use your mouse cursor to distinguish between tabs and spaces. Although you probably shouldn't be doing this, this behavior is less accurate/correct than selecting the entire block as a single unit.

A specific correctness issue with this behavior is that the entire block is copied to the clipboard as a tab literal if you select any of it, so two different visual selection ranges can produce the same clipboard content.

This particular behavior can be improved with "user-select: all", to instruct browsers to select the entire element as a single logical element. Now, selecting part of the tab selects the whole thing, as though it were really a tab literal.

(Some future change might abandon this approach and opt to use real tab literals with "tab-size" CSS, but we lose some ability to control alignment behavior if we do that and it doesn't have any obvious advantages over this approach other than cursor selection behavior.)

Test Plan:
  - In Safari and Firefox, dragged text to select a whitespace-expanded tab literal. Saw browsers select the whole sequence as though it were a single tab.
  - In Chorme, this also mostly works, but there's some glitchiness and flickering. I think this is still a net improvement, it's just not as smooth as Safari and Firefox.

Maniphest Tasks: T2495

Differential Revision: https://secure.phabricator.com/D21419
2020-07-17 15:10:06 -07:00
epriestley
1d4d860cb5 Allow non-authors to "Request Review" of draft revisions
Summary:
See PHI1810. In situations where:

  - An author submits an urgent change for review.
  - The author pings reviewers to ask them to look at it.

...the reviewers may not be able to move the review forward if the review is currently a "Draft". They can only "Commandeer" or ask the author to "Request Review" as ways forward.

Although I'm hesitant to support review actions (particularly, "Accept") on draft revisions, I think there's no harm in allowing reviewers to skip tests and promote the revision out of draft as an explicit action.

Additionally, lightly specialize some of the transaction strings to distinguish between "request review from draft" and other state transitions.

Test Plan:
  - As an author, used "Request Review" to promote a draft and to return a change to reviewers for consideration. These behaviors are unchanged, except "promote a draft" has different timeline text.
  - As a non-author, used "Begin Review" to promote a draft.

Differential Revision: https://secure.phabricator.com/D21403
2020-07-09 14:20:51 -07:00
epriestley
6e85b521fe Don't raise the "Subscribers Won't Be Notified" draft warning if you aren't adding any non-you subscribers
Summary:
Currently, adding subscribers to a draft revision raises a warning that they won't get an email/notification.

This warning has some false positives:

  - it triggers on any subscriber change, including removing subscribers; and
  - it triggers if you're only adding yourself as a subscriber.

Narrow the scope of the warning so it is raised only if you're adding a subscriber other than yourself.

Test Plan:
  - Added a non-self subscriber, got the warning as before.
  - Added self as a subscriber, no warning (previously: warning).
  - Removed a subscriber, no warning (previously: warning).

Differential Revision: https://secure.phabricator.com/D21402
2020-07-09 14:20:51 -07:00
epriestley
b21b73b8dd Expand Revision transaction API to allow actions to vary more broadly based on the viewer and revision state
Summary:
See PHI1810. Build toward support for "Request Review" by non-authors on drafts, to forcefully pull a revision out of draft.

Currently, some action strings can't vary based on revision state or the current viewer, so this "pull out of draft" action would have to either: say "Request Review"; or be a totally separate action.

Neither seem great, so allow the labels and messages to vary based on the viewer and revision state.

Test Plan: Grepped for affected symbols, see followup changes.

Differential Revision: https://secure.phabricator.com/D21401
2020-07-09 14:20:50 -07:00
epriestley
73c4240415 Add some additional patterns to the "filter Mercurial --debug output" list
Summary:
Modern Mercurial may emit some more patterns under "--debug".

This whole list is gross and can likely now be eliminated by increasing the minimum required Mercurial version (as `arc` has), but just paper over it for now.

Test Plan:
Locally, saw some views return to functional behavior that weren't previously working on a modern version of Mercurial.

The reproduction case is likely something in the vein of "repository is not writable by webserver, look at history view".

Differential Revision: https://secure.phabricator.com/D21398
2020-07-09 10:50:13 -07:00
epriestley
56838c0e3d Fix an issue where querying for a large number of projects by slug could paginate incorrectly
Summary:
See PHI1809. This query may join the "slug" table, but each project may have multiple slugs, and the query does not "GROUP BY" when this join occurs.

This may lead to partial result sets and unusual paging behavior.

This could likely be caught categorically in `loadAllFromArray()`; I'll adjust this in a followup.

Test Plan:
A minimal reproduction case is something like:

  - Give project P slugs: a, b, c.
  - Give project Q slugs: d.
  - Query for slugs: a, b, c, d; with limit 2.
  - Order the query so P returns first.
  - Expect: P and Q.
  - Actual: P generates 3 raw rows and the final result is just P with no pagination cursor.

Differential Revision: https://secure.phabricator.com/D21399
2020-07-09 10:50:03 -07:00
epriestley
205657ac76 Allow the Fact daemon to hibernate
Summary:
A handful of Phacility production shards have run into memory pressure issues recently. Although there's no smoking gun, and at least two other plausible contributors, one possible concern is that the Fact daemon was written before hibernation and can not currently hibernate. Even if there's no memory leak, this creates unnecessary memory pressure by holding the processes in memory.

Allow the Fact daemon to hibernate, like other daemons do.

Test Plan: Ran "bin/phd debug fact", saw the Fact daemon hibernate.

Subscribers: yelirekim

Differential Revision: https://secure.phabricator.com/D21389
2020-07-01 06:33:06 -07:00
epriestley
7d496f2c6d Collapse repository URI normalization code into Arcanist
Summary: Ref T13546. Companion change to D21372. Move URI normalization code to Arcanist to we can more-often resolve remote URIs correctly.

Test Plan: Grepped for affected symbols.

Maniphest Tasks: T13546

Differential Revision: https://secure.phabricator.com/D21373
2020-06-30 15:54:33 -07:00
epriestley
a7b3ba5a6f When long monospaced character sequences appear in Remarkup tables, break rather than scrolling
Summary:
Ref PHI1798. If you put an SSH public key in a table cell with monospaced formatting and then print the table, the cell scrolls and not all of the content appears in your physical printed document.

Generally, the current scrolling behavior for monospaced text seems never-desirable: I can't imagine any cases where we want the table cell to scroll. (There's more of an argument for complex cases where a table cell has, say, an embedded paste.)

Add `line-break: anywhere` to break monospaced text inside these cells.

Test Plan: In Safari, Firefox, and Chrome, viewed a ##|`MMMMM....`|## table. Saw scrolling before and wrapping/breaking after.

Differential Revision: https://secure.phabricator.com/D21370
2020-06-29 16:18:05 -07:00
epriestley
22de618d3b When acquiring a GlobalLock, put good connections that just got unlucky back in the pool
Summary:
See PHI1794, which describes a connection exhaustion issue with a large number of webhook tasks in queue.

The "GlobalLock" mechanism manages a separate connection pool from the main pool, and webhook workers immediately try to grab a webhook lock with a 0-second wait when they start. So far, this is fine.

Prior to this change, good connections which fail to acqiure a lock are discarded. This can lead to connection exhaustion as the worker rapidly cycles through lock attempts: the connections will remain open for at least 60 seconds (since D16389) in an effort to avoid outbound port exhaustion, but they're effectively orphaned because they aren't part of the main pool and aren't part of the lock pool. We're basically leaking a connection every time we fail to lock.

Failing to lock doesn't mean we need to discard the connection: it's a completely suitable connection for reuse. Instead of dropping it on the floor, put it into the lock pool.

Test Plan:
  - Used "bin/webhook call ... --count 10000 --background" to queue a large number of webhook calls against a slow ("sleep(15);") webhook.
  - Used "bin/phd launch 32 taskmaster" to start taskmasters.
  - Observed MySQL connection behavior:
    - Before change: 2048 configured connections immediately exhausted.
    - After change: connections stable at ~160ish.
  - Ran queue for a while, saw expected single-threaded calls to webhook.

Differential Revision: https://secure.phabricator.com/D21369
2020-06-25 18:06:09 -07:00
epriestley
d91abf50f7 Add "--background" and "--count" flags to "bin/webhook call"
Summary:
See PHI1794, which reports an issue where a large number of queued webhook calls led to connection exhaustion. To make this easier to reproduce and test, add "--count" and "--background" flags to "bin/webhook call".

This primarily supports "bin/webook call ... --background --count 10000" to quickly fill the queue with a bunch of calls.

Test Plan: Ran `bin/webhook call` in foreground and background modes, with and without counts. Saw appropriate console and queue behavior.

Differential Revision: https://secure.phabricator.com/D21368
2020-06-25 18:05:58 -07:00
epriestley
9ce1271805 Improve the quality of SSH error messages
Summary: See PHI1784. Currently, users who pass an invalid SSH command to Phabricator's SSH handler get an unhelpful error message. Make it more helpful.

Test Plan: Ran `./bin/ssh-exec` with no arguments (old, helpful error), invalid arguments (before: unhelpful error; after: helpful error), and valid arguments (old, helpful behavior).

Differential Revision: https://secure.phabricator.com/D21362
2020-06-16 08:59:36 -07:00
epriestley
8c7f114b4d Fix an issue where "Export Data" could fail if a user had a nonempty custom policy preference
Summary:
The "Export Data" workflow incorrectly uses the "Policy Favorites" setting to choose a default export format. This is just a copy/paste error; the correct setting exists and is unused.

If the setting value is an array (as the "Policy Favorites" value often is), we try to use it as an array index. This generates a runtime exception after D21044.

```
[2020-06-16 06:32:12] EXCEPTION: (RuntimeException) Illegal offset type in isset or empty at [<arcanist>/src/error/PhutilErrorHandler.php:263]
  #0 <#2> PhutilErrorHandler::handleError(integer, string, string, integer, array) called at [<phabricator>/src/applications/search/controller/PhabricatorApplicationSearchController.php:460]
```

  - Use the correct setting.
  - Make sure the value we read is a string.

Test Plan:
  - Used "Export Data" with a nonempty, array-valued "Policy Favorites" setting.
    - Before: runtime exception.
    - After: clean export.
  - Used "Export Data" again, saw my selection from the first time persisted.

Differential Revision: https://secure.phabricator.com/D21361
2020-06-16 06:44:23 -07:00
177 changed files with 3949 additions and 3239 deletions

View File

@@ -1,722 +0,0 @@
<?php
/**
*
* Copyright (c) 2011, Dan Myers.
* Parts copyright (c) 2008, Donovan Schonknecht.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) 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 OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* This is a modified BSD license (the third clause has been removed).
* The BSD license may be found here:
* http://www.opensource.org/licenses/bsd-license.php
*
* Amazon Simple Email Service is a trademark of Amazon.com, Inc. or its affiliates.
*
* SimpleEmailService is based on Donovan Schonknecht's Amazon S3 PHP class, found here:
* http://undesigned.org.za/2007/10/22/amazon-s3-php-class
*
*/
/**
* Amazon SimpleEmailService PHP class
*
* @link http://sourceforge.net/projects/php-aws-ses/
* version 0.8.1
*
*/
class SimpleEmailService
{
protected $__accessKey; // AWS Access key
protected $__secretKey; // AWS Secret key
protected $__host;
public function getAccessKey() { return $this->__accessKey; }
public function getSecretKey() { return $this->__secretKey; }
public function getHost() { return $this->__host; }
protected $__verifyHost = 1;
protected $__verifyPeer = 1;
// verifyHost and verifyPeer determine whether curl verifies ssl certificates.
// It may be necessary to disable these checks on certain systems.
// These only have an effect if SSL is enabled.
public function verifyHost() { return $this->__verifyHost; }
public function enableVerifyHost($enable = true) { $this->__verifyHost = $enable; }
public function verifyPeer() { return $this->__verifyPeer; }
public function enableVerifyPeer($enable = true) { $this->__verifyPeer = $enable; }
// If you use exceptions, errors will be communicated by throwing a
// SimpleEmailServiceException. By default, they will be trigger_error()'d.
protected $__useExceptions = 0;
public function useExceptions() { return $this->__useExceptions; }
public function enableUseExceptions($enable = true) { $this->__useExceptions = $enable; }
/**
* Constructor
*
* @param string $accessKey Access key
* @param string $secretKey Secret key
* @return void
*/
public function __construct($accessKey = null, $secretKey = null, $host = 'email.us-east-1.amazonaws.com') {
if (!function_exists('simplexml_load_string')) {
throw new Exception(
pht(
'The PHP SimpleXML extension is not available, but this '.
'extension is required to send mail via Amazon SES, because '.
'Amazon SES returns API responses in XML format. Install or '.
'enable the SimpleXML extension.'));
}
// Catch mistakes with reading the wrong column out of the SES
// documentation. See T10728.
if (preg_match('(-smtp)', $host)) {
throw new Exception(
pht(
'Amazon SES is not configured correctly: the configured SES '.
'endpoint ("%s") is an SMTP endpoint. Instead, use an API (HTTPS) '.
'endpoint.',
$host));
}
if ($accessKey !== null && $secretKey !== null) {
$this->setAuth($accessKey, $secretKey);
}
$this->__host = $host;
}
/**
* Set AWS access key and secret key
*
* @param string $accessKey Access key
* @param string $secretKey Secret key
* @return void
*/
public function setAuth($accessKey, $secretKey) {
$this->__accessKey = $accessKey;
$this->__secretKey = $secretKey;
}
/**
* Lists the email addresses that have been verified and can be used as the 'From' address
*
* @return An array containing two items: a list of verified email addresses, and the request id.
*/
public function listVerifiedEmailAddresses() {
$rest = new SimpleEmailServiceRequest($this, 'GET');
$rest->setParameter('Action', 'ListVerifiedEmailAddresses');
$rest = $rest->getResponse();
$response = array();
if(!isset($rest->body)) {
return $response;
}
$addresses = array();
foreach($rest->body->ListVerifiedEmailAddressesResult->VerifiedEmailAddresses->member as $address) {
$addresses[] = (string)$address;
}
$response['Addresses'] = $addresses;
$response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId;
return $response;
}
/**
* Requests verification of the provided email address, so it can be used
* as the 'From' address when sending emails through SimpleEmailService.
*
* After submitting this request, you should receive a verification email
* from Amazon at the specified address containing instructions to follow.
*
* @param string email The email address to get verified
* @return The request id for this request.
*/
public function verifyEmailAddress($email) {
$rest = new SimpleEmailServiceRequest($this, 'POST');
$rest->setParameter('Action', 'VerifyEmailAddress');
$rest->setParameter('EmailAddress', $email);
$rest = $rest->getResponse();
$response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId;
return $response;
}
/**
* Removes the specified email address from the list of verified addresses.
*
* @param string email The email address to remove
* @return The request id for this request.
*/
public function deleteVerifiedEmailAddress($email) {
$rest = new SimpleEmailServiceRequest($this, 'DELETE');
$rest->setParameter('Action', 'DeleteVerifiedEmailAddress');
$rest->setParameter('EmailAddress', $email);
$rest = $rest->getResponse();
$response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId;
return $response;
}
/**
* Retrieves information on the current activity limits for this account.
* See http://docs.amazonwebservices.com/ses/latest/APIReference/API_GetSendQuota.html
*
* @return An array containing information on this account's activity limits.
*/
public function getSendQuota() {
$rest = new SimpleEmailServiceRequest($this, 'GET');
$rest->setParameter('Action', 'GetSendQuota');
$rest = $rest->getResponse();
$response = array();
if(!isset($rest->body)) {
return $response;
}
$response['Max24HourSend'] = (string)$rest->body->GetSendQuotaResult->Max24HourSend;
$response['MaxSendRate'] = (string)$rest->body->GetSendQuotaResult->MaxSendRate;
$response['SentLast24Hours'] = (string)$rest->body->GetSendQuotaResult->SentLast24Hours;
$response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId;
return $response;
}
/**
* Retrieves statistics for the last two weeks of activity on this account.
* See http://docs.amazonwebservices.com/ses/latest/APIReference/API_GetSendStatistics.html
*
* @return An array of activity statistics. Each array item covers a 15-minute period.
*/
public function getSendStatistics() {
$rest = new SimpleEmailServiceRequest($this, 'GET');
$rest->setParameter('Action', 'GetSendStatistics');
$rest = $rest->getResponse();
$response = array();
if(!isset($rest->body)) {
return $response;
}
$datapoints = array();
foreach($rest->body->GetSendStatisticsResult->SendDataPoints->member as $datapoint) {
$p = array();
$p['Bounces'] = (string)$datapoint->Bounces;
$p['Complaints'] = (string)$datapoint->Complaints;
$p['DeliveryAttempts'] = (string)$datapoint->DeliveryAttempts;
$p['Rejects'] = (string)$datapoint->Rejects;
$p['Timestamp'] = (string)$datapoint->Timestamp;
$datapoints[] = $p;
}
$response['SendDataPoints'] = $datapoints;
$response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId;
return $response;
}
public function sendRawEmail($raw) {
$rest = new SimpleEmailServiceRequest($this, 'POST');
$rest->setParameter('Action', 'SendRawEmail');
$rest->setParameter('RawMessage.Data', base64_encode($raw));
$rest = $rest->getResponse();
$response['MessageId'] = (string)$rest->body->SendEmailResult->MessageId;
$response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId;
return $response;
}
/**
* Given a SimpleEmailServiceMessage object, submits the message to the service for sending.
*
* @return An array containing the unique identifier for this message and a separate request id.
* Returns false if the provided message is missing any required fields.
*/
public function sendEmail($sesMessage) {
if(!$sesMessage->validate()) {
return false;
}
$rest = new SimpleEmailServiceRequest($this, 'POST');
$rest->setParameter('Action', 'SendEmail');
$i = 1;
foreach($sesMessage->to as $to) {
$rest->setParameter('Destination.ToAddresses.member.'.$i, $to);
$i++;
}
if(is_array($sesMessage->cc)) {
$i = 1;
foreach($sesMessage->cc as $cc) {
$rest->setParameter('Destination.CcAddresses.member.'.$i, $cc);
$i++;
}
}
if(is_array($sesMessage->bcc)) {
$i = 1;
foreach($sesMessage->bcc as $bcc) {
$rest->setParameter('Destination.BccAddresses.member.'.$i, $bcc);
$i++;
}
}
if(is_array($sesMessage->replyto)) {
$i = 1;
foreach($sesMessage->replyto as $replyto) {
$rest->setParameter('ReplyToAddresses.member.'.$i, $replyto);
$i++;
}
}
$rest->setParameter('Source', $sesMessage->from);
if($sesMessage->returnpath != null) {
$rest->setParameter('ReturnPath', $sesMessage->returnpath);
}
if($sesMessage->subject != null && strlen($sesMessage->subject) > 0) {
$rest->setParameter('Message.Subject.Data', $sesMessage->subject);
if($sesMessage->subjectCharset != null && strlen($sesMessage->subjectCharset) > 0) {
$rest->setParameter('Message.Subject.Charset', $sesMessage->subjectCharset);
}
}
if($sesMessage->messagetext != null && strlen($sesMessage->messagetext) > 0) {
$rest->setParameter('Message.Body.Text.Data', $sesMessage->messagetext);
if($sesMessage->messageTextCharset != null && strlen($sesMessage->messageTextCharset) > 0) {
$rest->setParameter('Message.Body.Text.Charset', $sesMessage->messageTextCharset);
}
}
if($sesMessage->messagehtml != null && strlen($sesMessage->messagehtml) > 0) {
$rest->setParameter('Message.Body.Html.Data', $sesMessage->messagehtml);
if($sesMessage->messageHtmlCharset != null && strlen($sesMessage->messageHtmlCharset) > 0) {
$rest->setParameter('Message.Body.Html.Charset', $sesMessage->messageHtmlCharset);
}
}
$rest = $rest->getResponse();
$response['MessageId'] = (string)$rest->body->SendEmailResult->MessageId;
$response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId;
return $response;
}
/**
* Trigger an error message
*
* @internal Used by member functions to output errors
* @param array $error Array containing error information
* @return string
*/
public function __triggerError($functionname, $error)
{
if($error == false) {
$message = sprintf("SimpleEmailService::%s(): Encountered an error, but no description given", $functionname);
}
else if(isset($error['curl']) && $error['curl'])
{
$message = sprintf("SimpleEmailService::%s(): %s %s", $functionname, $error['code'], $error['message']);
}
else if(isset($error['Error']))
{
$e = $error['Error'];
$message = sprintf("SimpleEmailService::%s(): %s - %s: %s\nRequest Id: %s\n", $functionname, $e['Type'], $e['Code'], $e['Message'], $error['RequestId']);
}
if ($this->useExceptions()) {
throw new SimpleEmailServiceException($message);
} else {
trigger_error($message, E_USER_WARNING);
}
}
/**
* Callback handler for 503 retries.
*
* @internal Used by SimpleDBRequest to call the user-specified callback, if set
* @param $attempt The number of failed attempts so far
* @return The retry delay in microseconds, or 0 to stop retrying.
*/
public function __executeServiceTemporarilyUnavailableRetryDelay($attempt)
{
if(is_callable($this->__serviceUnavailableRetryDelayCallback)) {
$callback = $this->__serviceUnavailableRetryDelayCallback;
return $callback($attempt);
}
return 0;
}
}
final class SimpleEmailServiceRequest
{
private $ses, $verb, $parameters = array();
public $response;
/**
* Constructor
*
* @param string $ses The SimpleEmailService object making this request
* @param string $action action
* @param string $verb HTTP verb
* @return mixed
*/
function __construct($ses, $verb) {
$this->ses = $ses;
$this->verb = $verb;
$this->response = new STDClass;
$this->response->error = false;
}
/**
* Set request parameter
*
* @param string $key Key
* @param string $value Value
* @param boolean $replace Whether to replace the key if it already exists (default true)
* @return void
*/
public function setParameter($key, $value, $replace = true) {
if(!$replace && isset($this->parameters[$key]))
{
$temp = (array)($this->parameters[$key]);
$temp[] = $value;
$this->parameters[$key] = $temp;
}
else
{
$this->parameters[$key] = $value;
}
}
/**
* Get the response
*
* @return object | false
*/
public function getResponse() {
$params = array();
foreach ($this->parameters as $var => $value)
{
if(is_array($value))
{
foreach($value as $v)
{
$params[] = $var.'='.$this->__customUrlEncode($v);
}
}
else
{
$params[] = $var.'='.$this->__customUrlEncode($value);
}
}
sort($params, SORT_STRING);
// must be in format 'Sun, 06 Nov 1994 08:49:37 GMT'
$date = gmdate('D, d M Y H:i:s e');
$query = implode('&', $params);
$headers = array();
$headers[] = 'Date: '.$date;
$headers[] = 'Host: '.$this->ses->getHost();
$auth = 'AWS3-HTTPS AWSAccessKeyId='.$this->ses->getAccessKey();
$auth .= ',Algorithm=HmacSHA256,Signature='.$this->__getSignature($date);
$headers[] = 'X-Amzn-Authorization: '.$auth;
$url = 'https://'.$this->ses->getHost().'/';
// Basic setup
$curl = curl_init();
curl_setopt($curl, CURLOPT_USERAGENT, 'SimpleEmailService/php');
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, ($this->ses->verifyHost() ? 2 : 0));
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, ($this->ses->verifyPeer() ? 1 : 0));
// Request types
switch ($this->verb) {
case 'GET':
$url .= '?'.$query;
break;
case 'POST':
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->verb);
curl_setopt($curl, CURLOPT_POSTFIELDS, $query);
$headers[] = 'Content-Type: application/x-www-form-urlencoded';
break;
case 'DELETE':
$url .= '?'.$query;
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'DELETE');
break;
default: break;
}
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_HEADER, false);
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, false);
curl_setopt($curl, CURLOPT_WRITEFUNCTION, array(&$this, '__responseWriteCallback'));
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
// Execute, grab errors
if (!curl_exec($curl)) {
throw new SimpleEmailServiceException(
pht(
'Encountered an error while making an HTTP request to Amazon SES '.
'(cURL Error #%d): %s',
curl_errno($curl),
curl_error($curl)));
}
$this->response->code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
if ($this->response->code != 200) {
throw new SimpleEmailServiceException(
pht(
'Unexpected HTTP status while making request to Amazon SES: '.
'expected 200, got %s.',
$this->response->code));
}
@curl_close($curl);
// Parse body into XML
if ($this->response->error === false && isset($this->response->body)) {
$this->response->body = simplexml_load_string($this->response->body);
// Grab SES errors
if (!in_array($this->response->code, array(200, 201, 202, 204))
&& isset($this->response->body->Error)) {
$error = $this->response->body->Error;
$output = array();
$output['curl'] = false;
$output['Error'] = array();
$output['Error']['Type'] = (string)$error->Type;
$output['Error']['Code'] = (string)$error->Code;
$output['Error']['Message'] = (string)$error->Message;
$output['RequestId'] = (string)$this->response->body->RequestId;
$this->response->error = $output;
unset($this->response->body);
}
}
return $this->response;
}
/**
* CURL write callback
*
* @param resource &$curl CURL resource
* @param string &$data Data
* @return integer
*/
private function __responseWriteCallback(&$curl, &$data) {
if(!isset($this->response->body)) $this->response->body = '';
$this->response->body .= $data;
return strlen($data);
}
/**
* Contributed by afx114
* URL encode the parameters as per http://docs.amazonwebservices.com/AWSECommerceService/latest/DG/index.html?Query_QueryAuth.html
* PHP's rawurlencode() follows RFC 1738, not RFC 3986 as required by Amazon. The only difference is the tilde (~), so convert it back after rawurlencode
* See: http://www.morganney.com/blog/API/AWS-Product-Advertising-API-Requires-a-Signed-Request.php
*
* @param string $var String to encode
* @return string
*/
private function __customUrlEncode($var) {
return str_replace('%7E', '~', rawurlencode($var));
}
/**
* Generate the auth string using Hmac-SHA256
*
* @internal Used by SimpleDBRequest::getResponse()
* @param string $string String to sign
* @return string
*/
private function __getSignature($string) {
return base64_encode(hash_hmac('sha256', $string, $this->ses->getSecretKey(), true));
}
}
final class SimpleEmailServiceMessage {
// these are public for convenience only
// these are not to be used outside of the SimpleEmailService class!
public $to, $cc, $bcc, $replyto;
public $from, $returnpath;
public $subject, $messagetext, $messagehtml;
public $subjectCharset, $messageTextCharset, $messageHtmlCharset;
function __construct() {
$to = array();
$cc = array();
$bcc = array();
$replyto = array();
$from = null;
$returnpath = null;
$subject = null;
$messagetext = null;
$messagehtml = null;
$subjectCharset = null;
$messageTextCharset = null;
$messageHtmlCharset = null;
}
/**
* addTo, addCC, addBCC, and addReplyTo have the following behavior:
* If a single address is passed, it is appended to the current list of addresses.
* If an array of addresses is passed, that array is merged into the current list.
*/
function addTo($to) {
if(!is_array($to)) {
$this->to[] = $to;
}
else {
$this->to = array_merge($this->to, $to);
}
}
function addCC($cc) {
if(!is_array($cc)) {
$this->cc[] = $cc;
}
else {
$this->cc = array_merge($this->cc, $cc);
}
}
function addBCC($bcc) {
if(!is_array($bcc)) {
$this->bcc[] = $bcc;
}
else {
$this->bcc = array_merge($this->bcc, $bcc);
}
}
function addReplyTo($replyto) {
if(!is_array($replyto)) {
$this->replyto[] = $replyto;
}
else {
$this->replyto = array_merge($this->replyto, $replyto);
}
}
function setFrom($from) {
$this->from = $from;
}
function setReturnPath($returnpath) {
$this->returnpath = $returnpath;
}
function setSubject($subject) {
$this->subject = $subject;
}
function setSubjectCharset($charset) {
$this->subjectCharset = $charset;
}
function setMessageFromString($text, $html = null) {
$this->messagetext = $text;
$this->messagehtml = $html;
}
function setMessageFromFile($textfile, $htmlfile = null) {
if(file_exists($textfile) && is_file($textfile) && is_readable($textfile)) {
$this->messagetext = file_get_contents($textfile);
}
if(file_exists($htmlfile) && is_file($htmlfile) && is_readable($htmlfile)) {
$this->messagehtml = file_get_contents($htmlfile);
}
}
function setMessageFromURL($texturl, $htmlurl = null) {
$this->messagetext = file_get_contents($texturl);
if($htmlurl !== null) {
$this->messagehtml = file_get_contents($htmlurl);
}
}
function setMessageCharset($textCharset, $htmlCharset = null) {
$this->messageTextCharset = $textCharset;
$this->messageHtmlCharset = $htmlCharset;
}
/**
* Validates whether the message object has sufficient information to submit a request to SES.
* This does not guarantee the message will arrive, nor that the request will succeed;
* instead, it makes sure that no required fields are missing.
*
* This is used internally before attempting a SendEmail or SendRawEmail request,
* but it can be used outside of this file if verification is desired.
* May be useful if e.g. the data is being populated from a form; developers can generally
* use this function to verify completeness instead of writing custom logic.
*
* @return boolean
*/
public function validate() {
if(count($this->to) == 0)
return false;
if($this->from == null || strlen($this->from) == 0)
return false;
if($this->messagetext == null)
return false;
return true;
}
}
/**
* Thrown by SimpleEmailService when errors occur if you call
* enableUseExceptions(true).
*/
final class SimpleEmailServiceException extends Exception {
}

View File

@@ -9,13 +9,13 @@ return array(
'names' => array(
'conpherence.pkg.css' => '0e3cf785',
'conpherence.pkg.js' => '020aebcf',
'core.pkg.css' => '0fbedea0',
'core.pkg.js' => '74ad315f',
'core.pkg.css' => '704ac9c8',
'core.pkg.js' => '0c06d967',
'dark-console.pkg.js' => '187792c2',
'differential.pkg.css' => '5c459f92',
'differential.pkg.js' => '218fda21',
'differential.pkg.js' => '5080baf4',
'diffusion.pkg.css' => '42c75c37',
'diffusion.pkg.js' => 'a98c0bf7',
'diffusion.pkg.js' => '78c9885d',
'maniphest.pkg.css' => '35995d6d',
'maniphest.pkg.js' => 'c9308721',
'rsrc/audio/basic/alert.mp3' => '17889334',
@@ -73,7 +73,7 @@ return array(
'rsrc/css/application/diffusion/diffusion-icons.css' => '23b31a1b',
'rsrc/css/application/diffusion/diffusion-readme.css' => 'b68a76e4',
'rsrc/css/application/diffusion/diffusion-repository.css' => 'b89e8c6c',
'rsrc/css/application/diffusion/diffusion.css' => 'b54c77b0',
'rsrc/css/application/diffusion/diffusion.css' => 'e46232d6',
'rsrc/css/application/feed/feed.css' => 'd8b6e3f8',
'rsrc/css/application/files/global-drag-and-drop.css' => '1d2713a4',
'rsrc/css/application/flag/flag.css' => '2b77be8d',
@@ -113,14 +113,18 @@ return array(
'rsrc/css/application/slowvote/slowvote.css' => '1694baed',
'rsrc/css/application/tokens/tokens.css' => 'ce5a50bd',
'rsrc/css/application/uiexample/example.css' => 'b4795059',
'rsrc/css/core/core.css' => '1b29ed61',
'rsrc/css/core/remarkup.css' => 'c286eaef',
'rsrc/css/core/core.css' => 'b3ebd90d',
'rsrc/css/core/remarkup.css' => '24d48a73',
'rsrc/css/core/syntax.css' => '548567f6',
'rsrc/css/core/z-index.css' => 'ac3bfcd4',
'rsrc/css/diviner/diviner-shared.css' => '4bd263b0',
'rsrc/css/font/font-awesome.css' => '3883938a',
'rsrc/css/font/font-lato.css' => '23631304',
'rsrc/css/font/phui-font-icon-base.css' => '303c9b87',
'rsrc/css/fuel/fuel-grid.css' => '66697240',
'rsrc/css/fuel/fuel-handle-list.css' => '2c4cbeca',
'rsrc/css/fuel/fuel-map.css' => 'd6e31510',
'rsrc/css/fuel/fuel-menu.css' => '21f5d199',
'rsrc/css/layout/phabricator-source-code-view.css' => '03d7ac28',
'rsrc/css/phui/button/phui-button-bar.css' => 'a4aa75c4',
'rsrc/css/phui/button/phui-button-simple.css' => '1ff278aa',
@@ -133,11 +137,11 @@ return array(
'rsrc/css/phui/object-item/phui-oi-color.css' => 'b517bfa0',
'rsrc/css/phui/object-item/phui-oi-drag-ui.css' => 'da15d3dc',
'rsrc/css/phui/object-item/phui-oi-flush-ui.css' => '490e2e2e',
'rsrc/css/phui/object-item/phui-oi-list-view.css' => 'd7723ecc',
'rsrc/css/phui/object-item/phui-oi-list-view.css' => 'af98a277',
'rsrc/css/phui/object-item/phui-oi-simple-ui.css' => '6a30fa46',
'rsrc/css/phui/phui-action-list.css' => '1b0085b2',
'rsrc/css/phui/phui-action-panel.css' => '6c386cbf',
'rsrc/css/phui/phui-badge.css' => '666e25ad',
'rsrc/css/phui/phui-badge.css' => '96576409',
'rsrc/css/phui/phui-basic-nav-view.css' => '56ebd66d',
'rsrc/css/phui/phui-big-info-view.css' => '362ad37b',
'rsrc/css/phui/phui-box.css' => '5ed3b8cb',
@@ -188,6 +192,14 @@ return array(
'rsrc/css/sprite-tokens.css' => 'f1896dc5',
'rsrc/css/syntax/syntax-default.css' => '055fc231',
'rsrc/custom/css/phabricator-welcome-page.css' => 'a641fcc9',
'rsrc/custom/image/badges/badge_devfund_bronze.png' => '0f98ccf2',
'rsrc/custom/image/badges/badge_devfund_diamond.png' => '196d8206',
'rsrc/custom/image/badges/badge_devfund_gold.png' => 'd03e1722',
'rsrc/custom/image/badges/badge_devfund_platinum.png' => '3e517840',
'rsrc/custom/image/badges/badge_devfund_silver.png' => '73594dee',
'rsrc/custom/image/badges/badge_devfund_titanium.png' => 'e30aa898',
'rsrc/custom/image/badges/badge_sprite_fright.png' => '6f4b20e6',
'rsrc/custom/image/badges/badge_studio.png' => 'ffbdcabb',
'rsrc/custom/image/blender_logo.png' => '86dc8498',
'rsrc/externals/d3/d3.min.js' => '9d068042',
'rsrc/externals/font/fontawesome/fontawesome-webfont.eot' => '23f8c698',
@@ -381,7 +393,7 @@ return array(
'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => 'a2ab19be',
'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '1e413dc9',
'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => '0116d3e8',
'rsrc/js/application/diff/DiffChangeset.js' => '39dcf2c3',
'rsrc/js/application/diff/DiffChangeset.js' => '3b6e1fde',
'rsrc/js/application/diff/DiffChangesetList.js' => 'cc2c5de5',
'rsrc/js/application/diff/DiffInline.js' => '511a1315',
'rsrc/js/application/diff/DiffPathView.js' => '8207abf9',
@@ -392,7 +404,7 @@ return array(
'rsrc/js/application/diffusion/ExternalEditorLinkEngine.js' => '48a8641f',
'rsrc/js/application/diffusion/behavior-audit-preview.js' => 'b7b73831',
'rsrc/js/application/diffusion/behavior-commit-branches.js' => '4b671572',
'rsrc/js/application/diffusion/behavior-commit-graph.js' => 'ef836bf2',
'rsrc/js/application/diffusion/behavior-commit-graph.js' => 'ac10c917',
'rsrc/js/application/diffusion/behavior-locate-file.js' => '87428eb2',
'rsrc/js/application/diffusion/behavior-pull-lastmodified.js' => 'c715c123',
'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => '6a85bc5a',
@@ -475,7 +487,7 @@ return array(
'rsrc/js/core/behavior-choose-control.js' => '04f8a1e3',
'rsrc/js/core/behavior-copy.js' => 'cf32921f',
'rsrc/js/core/behavior-detect-timezone.js' => '78bc5d94',
'rsrc/js/core/behavior-device.js' => '0cf79f45',
'rsrc/js/core/behavior-device.js' => 'ac2b1e01',
'rsrc/js/core/behavior-drag-and-drop-textarea.js' => '7ad020a5',
'rsrc/js/core/behavior-fancy-datepicker.js' => '956f3eeb',
'rsrc/js/core/behavior-form.js' => '55d7b788',
@@ -569,13 +581,17 @@ return array(
'differential-revision-history-css' => '8aa3eac5',
'differential-revision-list-css' => '93d2df7d',
'differential-table-of-contents-css' => 'bba788b9',
'diffusion-css' => 'b54c77b0',
'diffusion-css' => 'e46232d6',
'diffusion-icons-css' => '23b31a1b',
'diffusion-readme-css' => 'b68a76e4',
'diffusion-repository-css' => 'b89e8c6c',
'diviner-shared-css' => '4bd263b0',
'font-fontawesome' => '3883938a',
'font-lato' => '23631304',
'fuel-grid-css' => '66697240',
'fuel-handle-list-css' => '2c4cbeca',
'fuel-map-css' => 'd6e31510',
'fuel-menu-css' => '21f5d199',
'global-drag-and-drop-css' => '1d2713a4',
'harbormaster-css' => '8dfe16b2',
'herald-css' => '648d39e2',
@@ -613,11 +629,11 @@ return array(
'javelin-behavior-day-view' => '727a5a61',
'javelin-behavior-desktop-notifications-control' => '070679fe',
'javelin-behavior-detect-timezone' => '78bc5d94',
'javelin-behavior-device' => '0cf79f45',
'javelin-behavior-device' => 'ac2b1e01',
'javelin-behavior-differential-diff-radios' => '925fe8cd',
'javelin-behavior-differential-populate' => 'b86ef6c2',
'javelin-behavior-diffusion-commit-branches' => '4b671572',
'javelin-behavior-diffusion-commit-graph' => 'ef836bf2',
'javelin-behavior-diffusion-commit-graph' => 'ac10c917',
'javelin-behavior-diffusion-locate-file' => '87428eb2',
'javelin-behavior-diffusion-pull-lastmodified' => 'c715c123',
'javelin-behavior-document-engine' => '243d6c22',
@@ -773,12 +789,12 @@ return array(
'phabricator-busy' => '5202e831',
'phabricator-chatlog-css' => 'abdc76ee',
'phabricator-content-source-view-css' => 'cdf0d579',
'phabricator-core-css' => '1b29ed61',
'phabricator-core-css' => 'b3ebd90d',
'phabricator-countdown-css' => 'bff8012f',
'phabricator-darklog' => '3b869402',
'phabricator-darkmessage' => '26cd4b73',
'phabricator-dashboard-css' => '5a205b9d',
'phabricator-diff-changeset' => '39dcf2c3',
'phabricator-diff-changeset' => '3b6e1fde',
'phabricator-diff-changeset-list' => 'cc2c5de5',
'phabricator-diff-inline' => '511a1315',
'phabricator-diff-path-view' => '8207abf9',
@@ -800,7 +816,7 @@ return array(
'phabricator-object-selector-css' => 'ee77366f',
'phabricator-phtize' => '2f1db1ed',
'phabricator-prefab' => '5793d835',
'phabricator-remarkup-css' => 'c286eaef',
'phabricator-remarkup-css' => '24d48a73',
'phabricator-search-results-css' => '9ea70ace',
'phabricator-shaped-request' => '995f5102',
'phabricator-slowvote-css' => '1694baed',
@@ -823,7 +839,7 @@ return array(
'phrequent-css' => 'bd79cc67',
'phriction-document-css' => '03380da0',
'phui-action-panel-css' => '6c386cbf',
'phui-badge-view-css' => '666e25ad',
'phui-badge-view-css' => '96576409',
'phui-basic-nav-view-css' => '56ebd66d',
'phui-big-info-view-css' => '362ad37b',
'phui-box-css' => '5ed3b8cb',
@@ -869,7 +885,7 @@ return array(
'phui-oi-color-css' => 'b517bfa0',
'phui-oi-drag-ui-css' => 'da15d3dc',
'phui-oi-flush-ui-css' => '490e2e2e',
'phui-oi-list-view-css' => 'd7723ecc',
'phui-oi-list-view-css' => 'af98a277',
'phui-oi-simple-ui-css' => '6a30fa46',
'phui-pager-css' => 'd022c7ad',
'phui-pinboard-view-css' => '1f08f5d8',
@@ -1005,13 +1021,6 @@ return array(
'javelin-dom',
'phabricator-draggable-list',
),
'0cf79f45' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'javelin-vector',
'javelin-install',
),
'0d2490ce' => array(
'javelin-install',
),
@@ -1231,7 +1240,14 @@ return array(
'trigger-rule',
'trigger-rule-type',
),
'39dcf2c3' => array(
'3ae89b20' => array(
'phui-workcard-view-css',
),
'3b4899b0' => array(
'javelin-behavior',
'phabricator-prefab',
),
'3b6e1fde' => array(
'javelin-dom',
'javelin-util',
'javelin-stratcom',
@@ -1245,13 +1261,6 @@ return array(
'phuix-button-view',
'javelin-external-editor-link-engine',
),
'3ae89b20' => array(
'phui-workcard-view-css',
),
'3b4899b0' => array(
'javelin-behavior',
'phabricator-prefab',
),
'3dc5ad43' => array(
'javelin-behavior',
'javelin-stratcom',
@@ -1924,6 +1933,18 @@ return array(
'javelin-dom',
'phabricator-notification',
),
'ac10c917' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
),
'ac2b1e01' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'javelin-vector',
'javelin-install',
),
'ad258e28' => array(
'javelin-behavior',
'javelin-dom',
@@ -2176,11 +2197,6 @@ return array(
'ee77366f' => array(
'aphront-dialog-view-css',
),
'ef836bf2' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
),
'f340a484' => array(
'javelin-install',
'javelin-dom',

View File

@@ -40,7 +40,8 @@ foreach ($lists as $list) {
if (!$username_okay) {
echo pht(
'Failed to migrate mailing list "%s": unable to generate a unique '.
'username for it.')."\n";
'username for it.',
$name)."\n";
continue;
}

View File

@@ -66,6 +66,8 @@ class Configuration {
$this->config_file = "$gitolite_root/conf/gitolite.conf";
$this->collectSystemPublicKeys();
$this->used_keys = array();
$this->committers = array();
if (!file_exists($this->config_file)) {
@@ -121,8 +123,10 @@ class Configuration {
$variable_name = trim($parts[0]);
if (array_key_exists($variable_name, $this->committers)) {
$system_committers = $this->getNonPhabtricatorUsers($parts[1]);
$committers = implode(' ', array_merge(
$system_committers, $this->committers[$variable_name]));
$all_committers = array_merge(
$system_committers, $this->committers[$variable_name]);
$unique_committers = array_unique($all_committers);
$committers = implode(' ', $unique_committers);
$line = "$variable_name = $committers";
}
}
@@ -339,7 +343,7 @@ function handleCustomPolicy($config, $policy) {
$policy_config_user_names = array();
$rule_type = $rule['rule'];
if ($rule_type == 'PhabricatorPolicyRuleUsers') {
if ($rule_type == 'PhabricatorUsersPolicyRule') {
$policy_config_user_names =
handleUsersPolicyRule($config, $rule);
} else if ($rule_type == 'PhabricatorProjectsPolicyRule') {

View File

@@ -211,21 +211,29 @@ try {
->setUniqueMethod('getName')
->execute();
$command_list = array_keys($workflows);
$command_list = implode(', ', $command_list);
$error_lines = array();
$error_lines[] = pht('Welcome to Phabricator.');
$error_lines[] = pht(
'You are logged in as %s.',
$user_name);
if (!$original_argv) {
throw new Exception(
pht(
"Welcome to Phabricator.\n\n".
"You are logged in as %s.\n\n".
"You haven't specified a command to run. This means you're requesting ".
"an interactive shell, but Phabricator does not provide an ".
"interactive shell over SSH.\n\n".
"Usually, you should run a command like `%s` or `%s` ".
"rather than connecting directly with SSH.\n\n".
"Supported commands are: %s.",
$user_name,
'git clone',
'hg push',
implode(', ', array_keys($workflows))));
$error_lines[] = pht(
'You have not specified a command to run. This means you are requesting '.
'an interactive shell, but Phabricator does not provide interactive '.
'shells over SSH.');
$error_lines[] = pht(
'(Usually, you should run a command like "git clone" or "hg push" '.
'instead of connecting directly with SSH.)');
$error_lines[] = pht(
'Supported commands are: %s.',
$command_list);
$error_lines = implode("\n\n", $error_lines);
throw new PhutilArgumentUsageException($error_lines);
}
$log_argv = implode(' ', $original_argv);
@@ -247,7 +255,20 @@ try {
$parsed_args = new PhutilArgumentParser($parseable_argv);
if (empty($workflows[$command])) {
throw new Exception(pht('Invalid command.'));
$error_lines[] = pht(
'You have specified the command "%s", but that command is not '.
'supported by Phabricator. As received by Phabricator, your entire '.
'argument list was:',
$command);
$error_lines[] = csprintf(' $ ssh ... -- %Ls', $parseable_argv);
$error_lines[] = pht(
'Supported commands are: %s.',
$command_list);
$error_lines = implode("\n\n", $error_lines);
throw new PhutilArgumentUsageException($error_lines);
}
$workflow = $parsed_args->parseWorkflows($workflows);

View File

@@ -770,6 +770,7 @@ phutil_register_library_map(array(
'DiffusionCommitEditEngine' => 'applications/diffusion/editor/DiffusionCommitEditEngine.php',
'DiffusionCommitFerretEngine' => 'applications/repository/search/DiffusionCommitFerretEngine.php',
'DiffusionCommitFulltextEngine' => 'applications/repository/search/DiffusionCommitFulltextEngine.php',
'DiffusionCommitGraphView' => 'applications/diffusion/view/DiffusionCommitGraphView.php',
'DiffusionCommitHasPackageEdgeType' => 'applications/diffusion/edge/DiffusionCommitHasPackageEdgeType.php',
'DiffusionCommitHasRevisionEdgeType' => 'applications/diffusion/edge/DiffusionCommitHasRevisionEdgeType.php',
'DiffusionCommitHasRevisionRelationship' => 'applications/diffusion/relationships/DiffusionCommitHasRevisionRelationship.php',
@@ -782,7 +783,6 @@ phutil_register_library_map(array(
'DiffusionCommitHookEngine' => 'applications/diffusion/engine/DiffusionCommitHookEngine.php',
'DiffusionCommitHookRejectException' => 'applications/diffusion/exception/DiffusionCommitHookRejectException.php',
'DiffusionCommitListController' => 'applications/diffusion/controller/DiffusionCommitListController.php',
'DiffusionCommitListView' => 'applications/diffusion/view/DiffusionCommitListView.php',
'DiffusionCommitMergeHeraldField' => 'applications/diffusion/herald/DiffusionCommitMergeHeraldField.php',
'DiffusionCommitMessageHeraldField' => 'applications/diffusion/herald/DiffusionCommitMessageHeraldField.php',
'DiffusionCommitPackageAuditHeraldField' => 'applications/diffusion/herald/DiffusionCommitPackageAuditHeraldField.php',
@@ -861,12 +861,8 @@ phutil_register_library_map(array(
'DiffusionGitWireProtocolCapabilities' => 'applications/diffusion/protocol/DiffusionGitWireProtocolCapabilities.php',
'DiffusionGitWireProtocolRef' => 'applications/diffusion/protocol/DiffusionGitWireProtocolRef.php',
'DiffusionGitWireProtocolRefList' => 'applications/diffusion/protocol/DiffusionGitWireProtocolRefList.php',
'DiffusionGraphController' => 'applications/diffusion/controller/DiffusionGraphController.php',
'DiffusionHistoryController' => 'applications/diffusion/controller/DiffusionHistoryController.php',
'DiffusionHistoryListView' => 'applications/diffusion/view/DiffusionHistoryListView.php',
'DiffusionHistoryQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php',
'DiffusionHistoryTableView' => 'applications/diffusion/view/DiffusionHistoryTableView.php',
'DiffusionHistoryView' => 'applications/diffusion/view/DiffusionHistoryView.php',
'DiffusionHovercardEngineExtension' => 'applications/diffusion/engineextension/DiffusionHovercardEngineExtension.php',
'DiffusionIdentityAssigneeDatasource' => 'applications/diffusion/typeahead/DiffusionIdentityAssigneeDatasource.php',
'DiffusionIdentityAssigneeEditField' => 'applications/diffusion/editfield/DiffusionIdentityAssigneeEditField.php',
@@ -877,6 +873,8 @@ phutil_register_library_map(array(
'DiffusionIdentityViewController' => 'applications/diffusion/controller/DiffusionIdentityViewController.php',
'DiffusionInlineCommentController' => 'applications/diffusion/controller/DiffusionInlineCommentController.php',
'DiffusionInternalAncestorsConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionInternalAncestorsConduitAPIMethod.php',
'DiffusionInternalCommitSearchConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionInternalCommitSearchConduitAPIMethod.php',
'DiffusionInternalCommitSearchEngine' => 'applications/audit/query/DiffusionInternalCommitSearchEngine.php',
'DiffusionInternalGitRawDiffQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionInternalGitRawDiffQueryConduitAPIMethod.php',
'DiffusionLastModifiedController' => 'applications/diffusion/controller/DiffusionLastModifiedController.php',
'DiffusionLastModifiedQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionLastModifiedQueryConduitAPIMethod.php',
@@ -1063,7 +1061,6 @@ phutil_register_library_map(array(
'DiffusionSyncLogSearchEngine' => 'applications/diffusion/query/DiffusionSyncLogSearchEngine.php',
'DiffusionTagListController' => 'applications/diffusion/controller/DiffusionTagListController.php',
'DiffusionTagListView' => 'applications/diffusion/view/DiffusionTagListView.php',
'DiffusionTagTableView' => 'applications/diffusion/view/DiffusionTagTableView.php',
'DiffusionTaggedRepositoriesFunctionDatasource' => 'applications/diffusion/typeahead/DiffusionTaggedRepositoriesFunctionDatasource.php',
'DiffusionTagsQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionTagsQueryConduitAPIMethod.php',
'DiffusionURIEditConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionURIEditConduitAPIMethod.php',
@@ -1310,6 +1307,17 @@ phutil_register_library_map(array(
'FlagDeleteConduitAPIMethod' => 'applications/flag/conduit/FlagDeleteConduitAPIMethod.php',
'FlagEditConduitAPIMethod' => 'applications/flag/conduit/FlagEditConduitAPIMethod.php',
'FlagQueryConduitAPIMethod' => 'applications/flag/conduit/FlagQueryConduitAPIMethod.php',
'FuelComponentView' => 'view/fuel/FuelComponentView.php',
'FuelGridCellView' => 'view/fuel/FuelGridCellView.php',
'FuelGridRowView' => 'view/fuel/FuelGridRowView.php',
'FuelGridView' => 'view/fuel/FuelGridView.php',
'FuelHandleListItemView' => 'view/fuel/FuelHandleListItemView.php',
'FuelHandleListView' => 'view/fuel/FuelHandleListView.php',
'FuelMapItemView' => 'view/fuel/FuelMapItemView.php',
'FuelMapView' => 'view/fuel/FuelMapView.php',
'FuelMenuItemView' => 'view/fuel/FuelMenuItemView.php',
'FuelMenuView' => 'view/fuel/FuelMenuView.php',
'FuelView' => 'view/fuel/FuelView.php',
'FundBacker' => 'applications/fund/storage/FundBacker.php',
'FundBackerCart' => 'applications/fund/phortune/FundBackerCart.php',
'FundBackerEditor' => 'applications/fund/editor/FundBackerEditor.php',
@@ -1422,12 +1430,16 @@ phutil_register_library_map(array(
'HarbormasterBuildStep' => 'applications/harbormaster/storage/configuration/HarbormasterBuildStep.php',
'HarbormasterBuildStepCoreCustomField' => 'applications/harbormaster/customfield/HarbormasterBuildStepCoreCustomField.php',
'HarbormasterBuildStepCustomField' => 'applications/harbormaster/customfield/HarbormasterBuildStepCustomField.php',
'HarbormasterBuildStepEditAPIMethod' => 'applications/harbormaster/conduit/HarbormasterBuildStepEditAPIMethod.php',
'HarbormasterBuildStepEditEngine' => 'applications/harbormaster/editor/HarbormasterBuildStepEditEngine.php',
'HarbormasterBuildStepEditor' => 'applications/harbormaster/editor/HarbormasterBuildStepEditor.php',
'HarbormasterBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterBuildStepGroup.php',
'HarbormasterBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterBuildStepImplementation.php',
'HarbormasterBuildStepImplementationTestCase' => 'applications/harbormaster/step/__tests__/HarbormasterBuildStepImplementationTestCase.php',
'HarbormasterBuildStepPHIDType' => 'applications/harbormaster/phid/HarbormasterBuildStepPHIDType.php',
'HarbormasterBuildStepQuery' => 'applications/harbormaster/query/HarbormasterBuildStepQuery.php',
'HarbormasterBuildStepSearchAPIMethod' => 'applications/harbormaster/conduit/HarbormasterBuildStepSearchAPIMethod.php',
'HarbormasterBuildStepSearchEngine' => 'applications/harbormaster/query/HarbormasterBuildStepSearchEngine.php',
'HarbormasterBuildStepTransaction' => 'applications/harbormaster/storage/configuration/HarbormasterBuildStepTransaction.php',
'HarbormasterBuildStepTransactionQuery' => 'applications/harbormaster/query/HarbormasterBuildStepTransactionQuery.php',
'HarbormasterBuildTarget' => 'applications/harbormaster/storage/build/HarbormasterBuildTarget.php',
@@ -1549,6 +1561,7 @@ phutil_register_library_map(array(
'HeraldBuildableState' => 'applications/herald/state/HeraldBuildableState.php',
'HeraldCallWebhookAction' => 'applications/herald/action/HeraldCallWebhookAction.php',
'HeraldCommentAction' => 'applications/herald/action/HeraldCommentAction.php',
'HeraldCommentContentField' => 'applications/herald/field/HeraldCommentContentField.php',
'HeraldCommitAdapter' => 'applications/diffusion/herald/HeraldCommitAdapter.php',
'HeraldCondition' => 'applications/herald/storage/HeraldCondition.php',
'HeraldConditionTranscript' => 'applications/herald/storage/transcript/HeraldConditionTranscript.php',
@@ -2166,12 +2179,14 @@ phutil_register_library_map(array(
'PasteSearchConduitAPIMethod' => 'applications/paste/conduit/PasteSearchConduitAPIMethod.php',
'PeopleBrowseUserDirectoryCapability' => 'applications/people/capability/PeopleBrowseUserDirectoryCapability.php',
'PeopleCreateUsersCapability' => 'applications/people/capability/PeopleCreateUsersCapability.php',
'PeopleDisableSpamUsersCapability' => 'applications/people/capability/PeopleDisableSpamUsersCapability.php',
'PeopleDisableUsersCapability' => 'applications/people/capability/PeopleDisableUsersCapability.php',
'PeopleHovercardEngineExtension' => 'applications/people/engineextension/PeopleHovercardEngineExtension.php',
'PeopleMainMenuBarExtension' => 'applications/people/engineextension/PeopleMainMenuBarExtension.php',
'PeopleUserLogGarbageCollector' => 'applications/people/garbagecollector/PeopleUserLogGarbageCollector.php',
'Phabricator404Controller' => 'applications/base/controller/Phabricator404Controller.php',
'PhabricatorAWSConfigOptions' => 'applications/config/option/PhabricatorAWSConfigOptions.php',
'PhabricatorAWSSESFuture' => 'applications/metamta/future/PhabricatorAWSSESFuture.php',
'PhabricatorAccessControlTestCase' => 'applications/base/controller/__tests__/PhabricatorAccessControlTestCase.php',
'PhabricatorAccessLog' => 'infrastructure/log/PhabricatorAccessLog.php',
'PhabricatorAccessLogConfigOptions' => 'applications/config/option/PhabricatorAccessLogConfigOptions.php',
@@ -2281,7 +2296,6 @@ phutil_register_library_map(array(
'PhabricatorAuditController' => 'applications/audit/controller/PhabricatorAuditController.php',
'PhabricatorAuditEditor' => 'applications/audit/editor/PhabricatorAuditEditor.php',
'PhabricatorAuditInlineComment' => 'applications/audit/storage/PhabricatorAuditInlineComment.php',
'PhabricatorAuditListView' => 'applications/audit/view/PhabricatorAuditListView.php',
'PhabricatorAuditMailReceiver' => 'applications/audit/mail/PhabricatorAuditMailReceiver.php',
'PhabricatorAuditManagementDeleteWorkflow' => 'applications/audit/management/PhabricatorAuditManagementDeleteWorkflow.php',
'PhabricatorAuditManagementWorkflow' => 'applications/audit/management/PhabricatorAuditManagementWorkflow.php',
@@ -4167,6 +4181,7 @@ phutil_register_library_map(array(
'PhabricatorPeopleRenameController' => 'applications/people/controller/PhabricatorPeopleRenameController.php',
'PhabricatorPeopleRevisionsProfileMenuItem' => 'applications/people/menuitem/PhabricatorPeopleRevisionsProfileMenuItem.php',
'PhabricatorPeopleSearchEngine' => 'applications/people/query/PhabricatorPeopleSearchEngine.php',
'PhabricatorPeopleSpamController' => 'applications/people/controller/PhabricatorPeopleSpamController.php',
'PhabricatorPeopleTasksProfileMenuItem' => 'applications/people/menuitem/PhabricatorPeopleTasksProfileMenuItem.php',
'PhabricatorPeopleTestDataGenerator' => 'applications/people/lipsum/PhabricatorPeopleTestDataGenerator.php',
'PhabricatorPeopleTransactionQuery' => 'applications/people/query/PhabricatorPeopleTransactionQuery.php',
@@ -4582,7 +4597,6 @@ phutil_register_library_map(array(
'PhabricatorRepositoryManagementImportingWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementImportingWorkflow.php',
'PhabricatorRepositoryManagementListPathsWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementListPathsWorkflow.php',
'PhabricatorRepositoryManagementListWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementListWorkflow.php',
'PhabricatorRepositoryManagementLookupUsersWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementLookupUsersWorkflow.php',
'PhabricatorRepositoryManagementMaintenanceWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementMaintenanceWorkflow.php',
'PhabricatorRepositoryManagementMarkImportedWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementMarkImportedWorkflow.php',
'PhabricatorRepositoryManagementMarkReachableWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementMarkReachableWorkflow.php',
@@ -4655,8 +4669,6 @@ phutil_register_library_map(array(
'PhabricatorRepositoryType' => 'applications/repository/constants/PhabricatorRepositoryType.php',
'PhabricatorRepositoryURI' => 'applications/repository/storage/PhabricatorRepositoryURI.php',
'PhabricatorRepositoryURIIndex' => 'applications/repository/storage/PhabricatorRepositoryURIIndex.php',
'PhabricatorRepositoryURINormalizer' => 'applications/repository/data/PhabricatorRepositoryURINormalizer.php',
'PhabricatorRepositoryURINormalizerTestCase' => 'applications/repository/data/__tests__/PhabricatorRepositoryURINormalizerTestCase.php',
'PhabricatorRepositoryURIPHIDType' => 'applications/repository/phid/PhabricatorRepositoryURIPHIDType.php',
'PhabricatorRepositoryURIQuery' => 'applications/repository/query/PhabricatorRepositoryURIQuery.php',
'PhabricatorRepositoryURITestCase' => 'applications/repository/storage/__tests__/PhabricatorRepositoryURITestCase.php',
@@ -5059,6 +5071,7 @@ phutil_register_library_map(array(
'PhabricatorUserCustomFieldNumericIndex' => 'applications/people/storage/PhabricatorUserCustomFieldNumericIndex.php',
'PhabricatorUserCustomFieldStringIndex' => 'applications/people/storage/PhabricatorUserCustomFieldStringIndex.php',
'PhabricatorUserDAO' => 'applications/people/storage/PhabricatorUserDAO.php',
'PhabricatorUserDisableSpamTransaction' => 'applications/people/xaction/PhabricatorUserDisableSpamTransaction.php',
'PhabricatorUserDisableTransaction' => 'applications/people/xaction/PhabricatorUserDisableTransaction.php',
'PhabricatorUserEditEngine' => 'applications/people/editor/PhabricatorUserEditEngine.php',
'PhabricatorUserEditor' => 'applications/people/editor/PhabricatorUserEditor.php',
@@ -5959,9 +5972,11 @@ phutil_register_library_map(array(
'celerity_get_resource_uri' => 'applications/celerity/api.php',
'hsprintf' => 'infrastructure/markup/render.php',
'javelin_tag' => 'infrastructure/javelin/markup.php',
'phabricator_absolute_datetime' => 'view/viewutils.php',
'phabricator_date' => 'view/viewutils.php',
'phabricator_datetime' => 'view/viewutils.php',
'phabricator_datetimezone' => 'view/viewutils.php',
'phabricator_dual_datetime' => 'view/viewutils.php',
'phabricator_form' => 'infrastructure/javelin/markup.php',
'phabricator_format_local_time' => 'view/viewutils.php',
'phabricator_relative_date' => 'view/viewutils.php',
@@ -6867,6 +6882,7 @@ phutil_register_library_map(array(
'DiffusionCommitEditEngine' => 'PhabricatorEditEngine',
'DiffusionCommitFerretEngine' => 'PhabricatorFerretEngine',
'DiffusionCommitFulltextEngine' => 'PhabricatorFulltextEngine',
'DiffusionCommitGraphView' => 'DiffusionView',
'DiffusionCommitHasPackageEdgeType' => 'PhabricatorEdgeType',
'DiffusionCommitHasRevisionEdgeType' => 'PhabricatorEdgeType',
'DiffusionCommitHasRevisionRelationship' => 'DiffusionCommitRelationship',
@@ -6879,7 +6895,6 @@ phutil_register_library_map(array(
'DiffusionCommitHookEngine' => 'Phobject',
'DiffusionCommitHookRejectException' => 'Exception',
'DiffusionCommitListController' => 'DiffusionController',
'DiffusionCommitListView' => 'AphrontView',
'DiffusionCommitMergeHeraldField' => 'DiffusionCommitHeraldField',
'DiffusionCommitMessageHeraldField' => 'DiffusionCommitHeraldField',
'DiffusionCommitPackageAuditHeraldField' => 'DiffusionCommitHeraldField',
@@ -6961,12 +6976,8 @@ phutil_register_library_map(array(
'DiffusionGitWireProtocolCapabilities' => 'Phobject',
'DiffusionGitWireProtocolRef' => 'Phobject',
'DiffusionGitWireProtocolRefList' => 'Phobject',
'DiffusionGraphController' => 'DiffusionController',
'DiffusionHistoryController' => 'DiffusionController',
'DiffusionHistoryListView' => 'DiffusionHistoryView',
'DiffusionHistoryQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
'DiffusionHistoryTableView' => 'DiffusionHistoryView',
'DiffusionHistoryView' => 'DiffusionView',
'DiffusionHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension',
'DiffusionIdentityAssigneeDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'DiffusionIdentityAssigneeEditField' => 'PhabricatorTokenizerEditField',
@@ -6977,6 +6988,8 @@ phutil_register_library_map(array(
'DiffusionIdentityViewController' => 'DiffusionController',
'DiffusionInlineCommentController' => 'PhabricatorInlineCommentController',
'DiffusionInternalAncestorsConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
'DiffusionInternalCommitSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'DiffusionInternalCommitSearchEngine' => 'PhabricatorApplicationSearchEngine',
'DiffusionInternalGitRawDiffQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
'DiffusionLastModifiedController' => 'DiffusionController',
'DiffusionLastModifiedQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
@@ -7162,7 +7175,6 @@ phutil_register_library_map(array(
'DiffusionSyncLogSearchEngine' => 'PhabricatorApplicationSearchEngine',
'DiffusionTagListController' => 'DiffusionController',
'DiffusionTagListView' => 'DiffusionView',
'DiffusionTagTableView' => 'DiffusionView',
'DiffusionTaggedRepositoriesFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'DiffusionTagsQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
'DiffusionURIEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
@@ -7455,6 +7467,17 @@ phutil_register_library_map(array(
'FlagDeleteConduitAPIMethod' => 'FlagConduitAPIMethod',
'FlagEditConduitAPIMethod' => 'FlagConduitAPIMethod',
'FlagQueryConduitAPIMethod' => 'FlagConduitAPIMethod',
'FuelComponentView' => 'FuelView',
'FuelGridCellView' => 'FuelComponentView',
'FuelGridRowView' => 'FuelView',
'FuelGridView' => 'FuelComponentView',
'FuelHandleListItemView' => 'FuelView',
'FuelHandleListView' => 'FuelComponentView',
'FuelMapItemView' => 'AphrontView',
'FuelMapView' => 'FuelComponentView',
'FuelMenuItemView' => 'FuelView',
'FuelMenuView' => 'FuelComponentView',
'FuelView' => 'AphrontView',
'FundBacker' => array(
'FundDAO',
'PhabricatorPolicyInterface',
@@ -7614,18 +7637,23 @@ phutil_register_library_map(array(
'PhabricatorApplicationTransactionInterface',
'PhabricatorPolicyInterface',
'PhabricatorCustomFieldInterface',
'PhabricatorConduitResultInterface',
),
'HarbormasterBuildStepCoreCustomField' => array(
'HarbormasterBuildStepCustomField',
'PhabricatorStandardCustomFieldInterface',
),
'HarbormasterBuildStepCustomField' => 'PhabricatorCustomField',
'HarbormasterBuildStepEditAPIMethod' => 'PhabricatorEditEngineAPIMethod',
'HarbormasterBuildStepEditEngine' => 'PhabricatorEditEngine',
'HarbormasterBuildStepEditor' => 'PhabricatorApplicationTransactionEditor',
'HarbormasterBuildStepGroup' => 'Phobject',
'HarbormasterBuildStepImplementation' => 'Phobject',
'HarbormasterBuildStepImplementationTestCase' => 'PhabricatorTestCase',
'HarbormasterBuildStepPHIDType' => 'PhabricatorPHIDType',
'HarbormasterBuildStepQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'HarbormasterBuildStepSearchAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'HarbormasterBuildStepSearchEngine' => 'PhabricatorApplicationSearchEngine',
'HarbormasterBuildStepTransaction' => 'PhabricatorApplicationTransaction',
'HarbormasterBuildStepTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'HarbormasterBuildTarget' => array(
@@ -7758,6 +7786,7 @@ phutil_register_library_map(array(
'HeraldBuildableState' => 'HeraldState',
'HeraldCallWebhookAction' => 'HeraldAction',
'HeraldCommentAction' => 'HeraldAction',
'HeraldCommentContentField' => 'HeraldField',
'HeraldCommitAdapter' => array(
'HeraldAdapter',
'HarbormasterBuildableAdapterInterface',
@@ -8473,12 +8502,14 @@ phutil_register_library_map(array(
'PasteSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'PeopleBrowseUserDirectoryCapability' => 'PhabricatorPolicyCapability',
'PeopleCreateUsersCapability' => 'PhabricatorPolicyCapability',
'PeopleDisableSpamUsersCapability' => 'PhabricatorPolicyCapability',
'PeopleDisableUsersCapability' => 'PhabricatorPolicyCapability',
'PeopleHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension',
'PeopleMainMenuBarExtension' => 'PhabricatorMainMenuBarExtension',
'PeopleUserLogGarbageCollector' => 'PhabricatorGarbageCollector',
'Phabricator404Controller' => 'PhabricatorController',
'PhabricatorAWSConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorAWSSESFuture' => 'PhutilAWSFuture',
'PhabricatorAccessControlTestCase' => 'PhabricatorTestCase',
'PhabricatorAccessLog' => 'Phobject',
'PhabricatorAccessLogConfigOptions' => 'PhabricatorApplicationConfigOptions',
@@ -8603,7 +8634,6 @@ phutil_register_library_map(array(
'PhabricatorAuditController' => 'PhabricatorController',
'PhabricatorAuditEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorAuditInlineComment' => 'PhabricatorInlineComment',
'PhabricatorAuditListView' => 'AphrontView',
'PhabricatorAuditMailReceiver' => 'PhabricatorObjectMailReceiver',
'PhabricatorAuditManagementDeleteWorkflow' => 'PhabricatorAuditManagementWorkflow',
'PhabricatorAuditManagementWorkflow' => 'PhabricatorManagementWorkflow',
@@ -10790,6 +10820,7 @@ phutil_register_library_map(array(
'PhabricatorPeopleRenameController' => 'PhabricatorPeopleController',
'PhabricatorPeopleRevisionsProfileMenuItem' => 'PhabricatorProfileMenuItem',
'PhabricatorPeopleSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorPeopleSpamController' => 'PhabricatorPeopleController',
'PhabricatorPeopleTasksProfileMenuItem' => 'PhabricatorProfileMenuItem',
'PhabricatorPeopleTestDataGenerator' => 'PhabricatorTestDataGenerator',
'PhabricatorPeopleTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
@@ -11315,7 +11346,6 @@ phutil_register_library_map(array(
'PhabricatorRepositoryManagementImportingWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
'PhabricatorRepositoryManagementListPathsWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
'PhabricatorRepositoryManagementListWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
'PhabricatorRepositoryManagementLookupUsersWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
'PhabricatorRepositoryManagementMaintenanceWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
'PhabricatorRepositoryManagementMarkImportedWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
'PhabricatorRepositoryManagementMarkReachableWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
@@ -11412,8 +11442,6 @@ phutil_register_library_map(array(
'PhabricatorConduitResultInterface',
),
'PhabricatorRepositoryURIIndex' => 'PhabricatorRepositoryDAO',
'PhabricatorRepositoryURINormalizer' => 'Phobject',
'PhabricatorRepositoryURINormalizerTestCase' => 'PhabricatorTestCase',
'PhabricatorRepositoryURIPHIDType' => 'PhabricatorPHIDType',
'PhabricatorRepositoryURIQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorRepositoryURITestCase' => 'PhabricatorTestCase',
@@ -11860,6 +11888,7 @@ phutil_register_library_map(array(
'PhabricatorUserCustomFieldNumericIndex' => 'PhabricatorCustomFieldNumericIndexStorage',
'PhabricatorUserCustomFieldStringIndex' => 'PhabricatorCustomFieldStringIndexStorage',
'PhabricatorUserDAO' => 'PhabricatorLiskDAO',
'PhabricatorUserDisableSpamTransaction' => 'PhabricatorUserTransactionType',
'PhabricatorUserDisableTransaction' => 'PhabricatorUserTransactionType',
'PhabricatorUserEditEngine' => 'PhabricatorEditEngine',
'PhabricatorUserEditor' => 'PhabricatorEditor',

View File

@@ -0,0 +1,75 @@
<?php
final class DiffusionInternalCommitSearchEngine
extends PhabricatorApplicationSearchEngine {
public function getResultTypeDescription() {
return pht('Diffusion Raw Commits');
}
public function getApplicationClassName() {
return 'PhabricatorDiffusionApplication';
}
public function newQuery() {
return new DiffusionCommitQuery();
}
protected function buildQueryFromParameters(array $map) {
$query = $this->newQuery();
if ($map['repositoryPHIDs']) {
$query->withRepositoryPHIDs($map['repositoryPHIDs']);
}
return $query;
}
protected function buildCustomSearchFields() {
return array(
id(new PhabricatorSearchDatasourceField())
->setLabel(pht('Repositories'))
->setKey('repositoryPHIDs')
->setDatasource(new DiffusionRepositoryFunctionDatasource())
->setDescription(pht('Find commits in particular repositories.')),
);
}
protected function getURI($path) {
return null;
}
protected function renderResultList(
array $commits,
PhabricatorSavedQuery $query,
array $handles) {
return null;
}
protected function getObjectWireFieldsForConduit(
$object,
array $field_extensions,
array $extension_data) {
$commit = $object;
$viewer = $this->requireViewer();
$repository = $commit->getRepository();
$identifier = $commit->getCommitIdentifier();
id(new DiffusionRepositoryClusterEngine())
->setViewer($viewer)
->setRepository($repository)
->synchronizeWorkingCopyBeforeRead();
$ref = id(new DiffusionLowLevelCommitQuery())
->setRepository($repository)
->withIdentifier($identifier)
->execute();
return array(
'ref' => $ref->newDictionary(),
);
}
}

View File

@@ -221,9 +221,9 @@ final class PhabricatorCommitSearchEngine
$bucket = $this->getResultBucket($query);
$template = id(new PhabricatorAuditListView())
$template = id(new DiffusionCommitGraphView())
->setViewer($viewer)
->setShowDrafts(true);
->setShowAuditors(true);
$views = array();
if ($bucket) {
@@ -235,37 +235,31 @@ final class PhabricatorCommitSearchEngine
foreach ($groups as $group) {
// Don't show groups in Dashboard Panels
if ($group->getObjects() || !$this->isPanelContext()) {
$views[] = id(clone $template)
$item_list = id(clone $template)
->setCommits($group->getObjects())
->newObjectItemListView();
$views[] = $item_list
->setHeader($group->getName())
->setNoDataString($group->getNoDataString())
->setCommits($group->getObjects());
->setNoDataString($group->getNoDataString());
}
}
} catch (Exception $ex) {
$this->addError($ex->getMessage());
}
} else {
$views[] = id(clone $template)
->setCommits($commits)
->setNoDataString(pht('No commits found.'));
}
if (!$views) {
$views[] = id(new PhabricatorAuditListView())
->setViewer($viewer)
$item_list = id(clone $template)
->setCommits($commits)
->newObjectItemListView();
$views[] = $item_list
->setNoDataString(pht('No commits found.'));
}
if (count($views) == 1) {
$list = head($views)->buildList();
} else {
$list = $views;
}
$result = new PhabricatorApplicationSearchResultView();
$result->setContent($list);
return $result;
return id(new PhabricatorApplicationSearchResultView())
->setContent($views);
}
protected function getNewUserBody() {

View File

@@ -1,179 +0,0 @@
<?php
final class PhabricatorAuditListView extends AphrontView {
private $commits = array();
private $header;
private $showDrafts;
private $noDataString;
private $highlightedAudits;
public function setNoDataString($no_data_string) {
$this->noDataString = $no_data_string;
return $this;
}
public function getNoDataString() {
return $this->noDataString;
}
public function setHeader($header) {
$this->header = $header;
return $this;
}
public function getHeader() {
return $this->header;
}
public function setShowDrafts($show_drafts) {
$this->showDrafts = $show_drafts;
return $this;
}
public function getShowDrafts() {
return $this->showDrafts;
}
/**
* These commits should have both commit data and audit requests attached.
*/
public function setCommits(array $commits) {
assert_instances_of($commits, 'PhabricatorRepositoryCommit');
$this->commits = mpull($commits, null, 'getPHID');
return $this;
}
public function getCommits() {
return $this->commits;
}
private function getCommitDescription($phid) {
if ($this->commits === null) {
return pht('(Unknown Commit)');
}
$commit = idx($this->commits, $phid);
if (!$commit) {
return pht('(Unknown Commit)');
}
$summary = $commit->getCommitData()->getSummary();
if (strlen($summary)) {
return $summary;
}
// No summary, so either this is still importing or just has an empty
// commit message.
if (!$commit->isImported()) {
return pht('(Importing Commit...)');
} else {
return pht('(Untitled Commit)');
}
}
public function render() {
$list = $this->buildList();
$list->setFlush(true);
return $list->render();
}
public function buildList() {
$viewer = $this->getViewer();
$rowc = array();
$phids = array();
foreach ($this->getCommits() as $commit) {
$phids[] = $commit->getPHID();
foreach ($commit->getAudits() as $audit) {
$phids[] = $audit->getAuditorPHID();
}
$author_phid = $commit->getAuthorPHID();
if ($author_phid) {
$phids[] = $author_phid;
}
}
$handles = $viewer->loadHandles($phids);
$show_drafts = $this->getShowDrafts();
$draft_icon = id(new PHUIIconView())
->setIcon('fa-comment yellow')
->addSigil('has-tooltip')
->setMetadata(
array(
'tip' => pht('Unsubmitted Comments'),
));
$list = new PHUIObjectItemListView();
foreach ($this->commits as $commit) {
$commit_phid = $commit->getPHID();
$commit_handle = $handles[$commit_phid];
$committed = null;
$commit_name = $commit_handle->getName();
$commit_link = $commit_handle->getURI();
$commit_desc = $this->getCommitDescription($commit_phid);
$committed = phabricator_datetime($commit->getEpoch(), $viewer);
$status = $commit->getAuditStatusObject();
$status_text = $status->getName();
$status_color = $status->getColor();
$status_icon = $status->getIcon();
$author_phid = $commit->getAuthorPHID();
if ($author_phid) {
$author_name = $handles[$author_phid]->renderLink();
} else {
$author_name = $commit->getCommitData()->getAuthorName();
}
$item = id(new PHUIObjectItemView())
->setObjectName($commit_name)
->setHeader($commit_desc)
->setHref($commit_link)
->setDisabled($commit->isUnreachable())
->addByline(pht('Author: %s', $author_name))
->addIcon('none', $committed);
if ($show_drafts) {
if ($commit->getHasDraft($viewer)) {
$item->addAttribute($draft_icon);
}
}
$audits = $commit->getAudits();
$auditor_phids = mpull($audits, 'getAuditorPHID');
if ($auditor_phids) {
$auditor_list = $handles->newSublist($auditor_phids)
->renderList()
->setAsInline(true);
} else {
$auditor_list = phutil_tag('em', array(), pht('None'));
}
$item->addAttribute(pht('Auditors: %s', $auditor_list));
if ($status_color) {
$item->setStatusIcon($status_icon.' '.$status_color, $status_text);
}
$list->addItem($item);
}
if ($this->noDataString) {
$list->setNoDataString($this->noDataString);
}
if ($this->header) {
$list->setHeader($this->header);
}
return $list;
}
}

View File

@@ -181,6 +181,12 @@ final class PhabricatorAuthPasswordEngine
$normal_password = phutil_utf8_strtolower($raw_password);
if (strlen($normal_password) >= $minimum_similarity) {
foreach ($normal_map as $term => $source) {
// See T2312. This may be required if the term list includes numeric
// strings like "12345", which will be cast to integers when used as
// array keys.
$term = phutil_string_cast($term);
if (strpos($term, $normal_password) === false &&
strpos($normal_password, $term) === false) {
continue;

View File

@@ -100,9 +100,17 @@ final class PhabricatorConduitAPIController
}
} catch (Exception $ex) {
$result = null;
$error_code = ($ex instanceof ConduitException
? 'ERR-CONDUIT-CALL'
: 'ERR-CONDUIT-CORE');
if ($ex instanceof ConduitException) {
$error_code = 'ERR-CONDUIT-CALL';
} else {
$error_code = 'ERR-CONDUIT-CORE';
// See T13581. When a Conduit method raises an uncaught exception
// other than a "ConduitException", log it.
phlog($ex);
}
$error_info = $ex->getMessage();
}

View File

@@ -142,6 +142,8 @@ abstract class PhabricatorConduitController extends PhabricatorController {
$parts[] = '--conduit-token ';
$parts[] = phutil_tag('strong', array(), '<conduit-token>');
$parts[] = ' ';
$parts[] = '--';
$parts[] = ' ';
$parts[] = $method->getAPIMethodName();

View File

@@ -120,9 +120,21 @@ abstract class ConduitAPIMethod
public function executeMethod(ConduitAPIRequest $request) {
$this->setViewer($request->getUser());
$client = $this->newConduitCallProxyClient($request);
if ($client) {
// We're proxying, so just make an intracluster call.
return $client->callMethodSynchronous(
$this->getAPIMethodName(),
$request->getAllParameters());
}
return $this->execute($request);
}
protected function newConduitCallProxyClient(ConduitAPIRequest $request) {
return null;
}
abstract public function getAPIMethodName();
/**

View File

@@ -51,6 +51,10 @@ final class ConduitAPIRequest extends Phobject {
return $this->user;
}
public function getViewer() {
return $this->getUser();
}
public function setOAuthToken(
PhabricatorOAuthServerAccessToken $oauth_token) {
$this->oauthToken = $oauth_token;

View File

@@ -8,14 +8,21 @@ final class PhabricatorDaemonsSetupCheck extends PhabricatorSetupCheck {
protected function executeChecks() {
$task_daemon = id(new PhabricatorDaemonLogQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withStatus(PhabricatorDaemonLogQuery::STATUS_ALIVE)
->withDaemonClasses(array('PhabricatorTaskmasterDaemon'))
->setLimit(1)
->execute();
try {
$task_daemons = id(new PhabricatorDaemonLogQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withStatus(PhabricatorDaemonLogQuery::STATUS_ALIVE)
->withDaemonClasses(array('PhabricatorTaskmasterDaemon'))
->setLimit(1)
->execute();
if (!$task_daemon) {
$no_daemons = !$task_daemons;
} catch (Exception $ex) {
// Just skip this warning if the query fails for some reason.
$no_daemons = false;
}
if ($no_daemons) {
$doc_href = PhabricatorEnv::getDoclink('Managing Daemons with phd');
$summary = pht(

View File

@@ -322,6 +322,9 @@ final class PhabricatorExtraConfigSetupCheck extends PhabricatorSetupCheck {
'directly supported. Prefixes and other strings may be customized with '.
'"translation.override".');
$phd_reason = pht(
'Use "bin/phd debug ..." to get a detailed daemon execution log.');
$ancient_config += array(
'phid.external-loaders' =>
pht(
@@ -539,6 +542,9 @@ final class PhabricatorExtraConfigSetupCheck extends PhabricatorSetupCheck {
'phd.pid-directory' => pht(
'Phabricator daemons no longer use PID files.'),
'phd.trace' => $phd_reason,
'phd.verbose' => $phd_reason,
);
return $ancient_config;

View File

@@ -43,22 +43,6 @@ final class PhabricatorPHDConfigOptions
"configuration changes are picked up by the daemons ".
"automatically, but pool sizes can not be changed without a ".
"restart.")),
$this->newOption('phd.verbose', 'bool', false)
->setLocked(true)
->setBoolOptions(
array(
pht('Verbose mode'),
pht('Normal mode'),
))
->setSummary(pht("Launch daemons in 'verbose' mode by default."))
->setDescription(
pht(
"Launch daemons in 'verbose' mode by default. This creates a lot ".
"of output, but can help debug issues. Daemons launched in debug ".
"mode with '%s' are always launched in verbose mode. ".
"See also '%s'.",
'phd debug',
'phd.trace')),
$this->newOption('phd.user', 'string', null)
->setLocked(true)
->setSummary(pht('System user to run daemons as.'))
@@ -68,22 +52,6 @@ final class PhabricatorPHDConfigOptions
'user will own the working copies of any repositories that '.
'Phabricator imports or manages. This option is new and '.
'experimental.')),
$this->newOption('phd.trace', 'bool', false)
->setLocked(true)
->setBoolOptions(
array(
pht('Trace mode'),
pht('Normal mode'),
))
->setSummary(pht("Launch daemons in 'trace' mode by default."))
->setDescription(
pht(
"Launch daemons in 'trace' mode by default. This creates an ".
"ENORMOUS amount of output, but can help debug issues. Daemons ".
"launched in debug mode with '%s' are always launched in ".
"trace mode. See also '%s'.",
'phd debug',
'phd.verbose')),
$this->newOption('phd.garbage-collection', 'wild', array())
->setLocked(true)
->setLockedMessage(

View File

@@ -116,11 +116,11 @@ abstract class PhabricatorDaemonManagementWorkflow
$trace = PhutilArgumentParser::isTraceModeEnabled();
$flags = array();
if ($trace || PhabricatorEnv::getEnvConfig('phd.trace')) {
if ($trace) {
$flags[] = '--trace';
}
if ($debug || PhabricatorEnv::getEnvConfig('phd.verbose')) {
if ($debug) {
$flags[] = '--verbose';
}

View File

@@ -510,6 +510,11 @@ final class DifferentialRevisionViewController
->setLoadEntireGraph(true)
->loadGraph();
if (!$stack_graph->isEmpty()) {
// See PHI1900. The graph UI element now tries to figure out the correct
// height automatically, but currently can't in this case because the
// element is not visible when the page loads. Set an explicit height.
$stack_graph->setHeight(34);
$stack_table = $stack_graph->newGraphTable();
$parent_type = DifferentialRevisionDependsOnRevisionEdgeType::EDGECONST;

View File

@@ -36,7 +36,8 @@ final class PhabricatorDifferentialRebuildChangesetsWorkflow
throw new PhutilArgumentUsageException(
pht(
'Object "%s" specified by "--revision" must be a Differential '.
'revision.'));
'revision.',
$revision_identifier));
}
} else {
$revision = id(new DifferentialRevisionQuery())

View File

@@ -1053,14 +1053,30 @@ final class DifferentialChangesetParser extends Phobject {
$this->comments = id(new PHUIDiffInlineThreader())
->reorderAndThreadCommments($this->comments);
$old_max_display = 1;
foreach ($this->old as $old) {
if (isset($old['line'])) {
$old_max_display = $old['line'];
}
}
$new_max_display = 1;
foreach ($this->new as $new) {
if (isset($new['line'])) {
$new_max_display = $new['line'];
}
}
foreach ($this->comments as $comment) {
$final = $comment->getLineNumber() +
$comment->getLineLength();
$final = max(1, $final);
$display_line = $comment->getLineNumber() + $comment->getLineLength();
$display_line = max(1, $display_line);
if ($this->isCommentOnRightSideWhenDisplayed($comment)) {
$new_comments[$final][] = $comment;
$display_line = min($new_max_display, $display_line);
$new_comments[$display_line][] = $comment;
} else {
$old_comments[$final][] = $comment;
$display_line = min($old_max_display, $display_line);
$old_comments[$display_line][] = $comment;
}
}
}
@@ -1327,6 +1343,10 @@ final class DifferentialChangesetParser extends Phobject {
$not_covered = 0;
foreach ($this->new as $k => $new) {
if ($new === null) {
continue;
}
if (!$new['line']) {
continue;
}

View File

@@ -778,12 +778,19 @@ final class DifferentialRevisionQuery
*/
protected function shouldGroupQueryResultRows() {
$join_triggers = array_merge(
$this->pathIDs,
$this->ccs,
$this->reviewers);
if (count($this->pathIDs) > 1) {
return true;
}
if (count($join_triggers) > 1) {
if (count($this->ccs) > 1) {
return true;
}
if (count($this->reviewers) > 1) {
return true;
}
if (count($this->commitHashes) > 1) {
return true;
}

View File

@@ -83,7 +83,9 @@ final class DifferentialChangesetOneUpRenderer
$cells = array();
if ($is_old) {
if ($p['htype']) {
if (empty($p['oline'])) {
if ($p['htype'] === '\\') {
$class = 'comment';
} else if (empty($p['oline'])) {
$class = 'left old old-full';
} else {
$class = 'left old';
@@ -129,7 +131,9 @@ final class DifferentialChangesetOneUpRenderer
$cells[] = $no_coverage;
} else {
if ($p['htype']) {
if (empty($p['oline'])) {
if ($p['htype'] === '\\') {
$class = 'comment';
} else if (empty($p['oline'])) {
$class = 'right new new-full';
} else {
$class = 'right new';

View File

@@ -505,6 +505,8 @@ abstract class DifferentialChangesetRenderer extends Phobject {
$ospec['htype'] = $old[$ii]['type'];
if (isset($old_render[$ii])) {
$ospec['render'] = $old_render[$ii];
} else if ($ospec['htype'] === '\\') {
$ospec['render'] = $old[$ii]['text'];
}
}
@@ -514,6 +516,8 @@ abstract class DifferentialChangesetRenderer extends Phobject {
$nspec['htype'] = $new[$ii]['type'];
if (isset($new_render[$ii])) {
$nspec['render'] = $new_render[$ii];
} else if ($nspec['htype'] === '\\') {
$nspec['render'] = $new[$ii]['text'];
}
}

View File

@@ -747,9 +747,10 @@ final class DifferentialDiff
$prop->delete();
}
$viewstates = id(new DifferentialViewStateQuery())
$viewstate_query = id(new DifferentialViewStateQuery())
->setViewer($viewer)
->withObjectPHIDs(array($this->getPHID()));
$viewstates = new PhabricatorQueryIterator($viewstate_query);
foreach ($viewstates as $viewstate) {
$viewstate->delete();
}

View File

@@ -1033,9 +1033,10 @@ final class DifferentialRevision extends DifferentialDAO
$dummy_path->getTableName(),
$this->getID());
$viewstates = id(new DifferentialViewStateQuery())
$viewstate_query = id(new DifferentialViewStateQuery())
->setViewer($viewer)
->withObjectPHIDs(array($this->getPHID()));
$viewstates = new PhabricatorQueryIterator($viewstate_query);
foreach ($viewstates as $viewstate) {
$viewstate->delete();
}

View File

@@ -6,12 +6,15 @@ final class DifferentialRevisionAbandonTransaction
const TRANSACTIONTYPE = 'differential.revision.abandon';
const ACTIONKEY = 'abandon';
protected function getRevisionActionLabel() {
protected function getRevisionActionLabel(
DifferentialRevision $revision,
PhabricatorUser $viewer) {
return pht('Abandon Revision');
}
protected function getRevisionActionDescription(
DifferentialRevision $revision) {
DifferentialRevision $revision,
PhabricatorUser $viewer) {
return pht('This revision will be abandoned and closed.');
}

View File

@@ -6,12 +6,15 @@ final class DifferentialRevisionAcceptTransaction
const TRANSACTIONTYPE = 'differential.revision.accept';
const ACTIONKEY = 'accept';
protected function getRevisionActionLabel() {
protected function getRevisionActionLabel(
DifferentialRevision $revision,
PhabricatorUser $viewer) {
return pht('Accept Revision');
}
protected function getRevisionActionDescription(
DifferentialRevision $revision) {
DifferentialRevision $revision,
PhabricatorUser $viewer) {
return pht('These changes will be approved.');
}

View File

@@ -17,7 +17,9 @@ abstract class DifferentialRevisionActionTransaction
}
abstract protected function validateAction($object, PhabricatorUser $viewer);
abstract protected function getRevisionActionLabel();
abstract protected function getRevisionActionLabel(
DifferentialRevision $revision,
PhabricatorUser $viewer);
protected function validateOptionValue($object, $actor, array $value) {
return null;
@@ -53,15 +55,23 @@ abstract class DifferentialRevisionActionTransaction
}
protected function getRevisionActionDescription(
DifferentialRevision $revision) {
DifferentialRevision $revision,
PhabricatorUser $viewer) {
return null;
}
protected function getRevisionActionSubmitButtonText(
DifferentialRevision $revision) {
DifferentialRevision $revision,
PhabricatorUser $viewer) {
return null;
}
protected function getRevisionActionMetadata(
DifferentialRevision $revision,
PhabricatorUser $viewer) {
return array();
}
public static function loadAllActions() {
return id(new PhutilClassMapQuery())
->setAncestorClass(__CLASS__)
@@ -105,17 +115,19 @@ abstract class DifferentialRevisionActionTransaction
->setValue(true);
if ($this->isActionAvailable($revision, $viewer)) {
$label = $this->getRevisionActionLabel();
$label = $this->getRevisionActionLabel($revision, $viewer);
if ($label !== null) {
$field->setCommentActionLabel($label);
$description = $this->getRevisionActionDescription($revision);
$description = $this->getRevisionActionDescription($revision, $viewer);
$field->setActionDescription($description);
$group_key = $this->getRevisionActionGroupKey();
$field->setCommentActionGroupKey($group_key);
$button_text = $this->getRevisionActionSubmitButtonText($revision);
$button_text = $this->getRevisionActionSubmitButtonText(
$revision,
$viewer);
$field->setActionSubmitButtonText($button_text);
// Currently, every revision action conflicts with every other
@@ -144,6 +156,11 @@ abstract class DifferentialRevisionActionTransaction
$field->setOptions($options);
$field->setValue($value);
}
$metadata = $this->getRevisionActionMetadata($revision, $viewer);
foreach ($metadata as $metadata_key => $metadata_value) {
$field->setMetadataValue($metadata_key, $metadata_value);
}
}
}

View File

@@ -6,12 +6,15 @@ final class DifferentialRevisionCloseTransaction
const TRANSACTIONTYPE = 'differential.revision.close';
const ACTIONKEY = 'close';
protected function getRevisionActionLabel() {
protected function getRevisionActionLabel(
DifferentialRevision $revision,
PhabricatorUser $viewer) {
return pht('Close Revision');
}
protected function getRevisionActionDescription(
DifferentialRevision $revision) {
DifferentialRevision $revision,
PhabricatorUser $viewer) {
return pht('This revision will be closed.');
}

View File

@@ -6,12 +6,15 @@ final class DifferentialRevisionCommandeerTransaction
const TRANSACTIONTYPE = 'differential.revision.commandeer';
const ACTIONKEY = 'commandeer';
protected function getRevisionActionLabel() {
protected function getRevisionActionLabel(
DifferentialRevision $revision,
PhabricatorUser $viewer) {
return pht('Commandeer Revision');
}
protected function getRevisionActionDescription(
DifferentialRevision $revision) {
DifferentialRevision $revision,
PhabricatorUser $viewer) {
return pht('You will take control of this revision and become its author.');
}

View File

@@ -6,12 +6,15 @@ final class DifferentialRevisionPlanChangesTransaction
const TRANSACTIONTYPE = 'differential.revision.plan';
const ACTIONKEY = 'plan-changes';
protected function getRevisionActionLabel() {
protected function getRevisionActionLabel(
DifferentialRevision $revision,
PhabricatorUser $viewer) {
return pht('Plan Changes');
}
protected function getRevisionActionDescription(
DifferentialRevision $revision) {
DifferentialRevision $revision,
PhabricatorUser $viewer) {
return pht(
'This revision will be removed from review queues until it is revised.');
}

View File

@@ -6,12 +6,15 @@ final class DifferentialRevisionReclaimTransaction
const TRANSACTIONTYPE = 'differential.revision.reclaim';
const ACTIONKEY = 'reclaim';
protected function getRevisionActionLabel() {
protected function getRevisionActionLabel(
DifferentialRevision $revision,
PhabricatorUser $viewer) {
return pht('Reclaim Revision');
}
protected function getRevisionActionDescription(
DifferentialRevision $revision) {
DifferentialRevision $revision,
PhabricatorUser $viewer) {
return pht('This revision will be reclaimed and reopened.');
}

View File

@@ -6,12 +6,15 @@ final class DifferentialRevisionRejectTransaction
const TRANSACTIONTYPE = 'differential.revision.reject';
const ACTIONKEY = 'reject';
protected function getRevisionActionLabel() {
protected function getRevisionActionLabel(
DifferentialRevision $revision,
PhabricatorUser $viewer) {
return pht('Request Changes');
}
protected function getRevisionActionDescription(
DifferentialRevision $revision) {
DifferentialRevision $revision,
PhabricatorUser $viewer) {
return pht('This revision will be returned to the author for updates.');
}

View File

@@ -6,12 +6,15 @@ final class DifferentialRevisionReopenTransaction
const TRANSACTIONTYPE = 'differential.revision.reopen';
const ACTIONKEY = 'reopen';
protected function getRevisionActionLabel() {
protected function getRevisionActionLabel(
DifferentialRevision $revision,
PhabricatorUser $viewer) {
return pht('Reopen Revision');
}
protected function getRevisionActionDescription(
DifferentialRevision $revision) {
DifferentialRevision $revision,
PhabricatorUser $viewer) {
return pht('This revision will be reopened for review.');
}

View File

@@ -84,7 +84,8 @@ final class DifferentialRevisionRepositoryTransaction
$errors[] = $this->newInvalidError(
pht(
'Repository "%s" is not a valid repository, or you do not have '.
'permission to view it.'),
'permission to view it.',
$new_value),
$xaction);
}
}

View File

@@ -6,21 +6,63 @@ final class DifferentialRevisionRequestReviewTransaction
const TRANSACTIONTYPE = 'differential.revision.request';
const ACTIONKEY = 'request-review';
protected function getRevisionActionLabel() {
const SOURCE_HARBORMASTER = 'harbormaster';
const SOURCE_AUTHOR = 'author';
const SOURCE_VIEWER = 'viewer';
protected function getRevisionActionLabel(
DifferentialRevision $revision,
PhabricatorUser $viewer) {
// See PHI1810. Allow non-authors to "Request Review" on draft revisions
// to promote them out of the draft state. This smoothes over the workflow
// where an author asks for review of an urgent change but has not used
// "Request Review" to skip builds.
if ($revision->isDraft()) {
if (!$this->isViewerRevisionAuthor($revision, $viewer)) {
return pht('Begin Review Now');
}
}
return pht('Request Review');
}
protected function getRevisionActionDescription(
DifferentialRevision $revision) {
DifferentialRevision $revision,
PhabricatorUser $viewer) {
if ($revision->isDraft()) {
return pht('This revision will be submitted to reviewers for feedback.');
if (!$this->isViewerRevisionAuthor($revision, $viewer)) {
return pht(
'This revision will be moved out of the draft state so you can '.
'review it immediately.');
} else {
return pht(
'This revision will be submitted to reviewers for feedback.');
}
} else {
return pht('This revision will be returned to reviewers for feedback.');
}
}
protected function getRevisionActionMetadata(
DifferentialRevision $revision,
PhabricatorUser $viewer) {
$map = array();
if ($revision->isDraft()) {
$action_source = $this->getActorSourceType(
$revision,
$viewer);
$map['promotion.source'] = $action_source;
}
return $map;
}
protected function getRevisionActionSubmitButtonText(
DifferentialRevision $revision) {
DifferentialRevision $revision,
PhabricatorUser $viewer) {
// See PHI975. When the action stack will promote the revision out of
// draft, change the button text from "Submit Quietly".
@@ -72,29 +114,43 @@ final class DifferentialRevisionRequestReviewTransaction
'revisions.'));
}
// When revisions automatically promote out of "Draft" after builds finish,
// the viewer may be acting as the Harbormaster application.
if (!$viewer->isOmnipotent()) {
if (!$this->isViewerRevisionAuthor($object, $viewer)) {
throw new Exception(
pht(
'You can not request review of this revision because you are not '.
'the author of the revision.'));
}
}
$this->getActorSourceType($object, $viewer);
}
public function getTitle() {
return pht(
'%s requested review of this revision.',
$this->renderAuthor());
$source = $this->getDraftPromotionSource();
switch ($source) {
case self::SOURCE_HARBORMASTER:
case self::SOURCE_VIEWER:
case self::SOURCE_AUTHOR:
return pht(
'%s published this revision for review.',
$this->renderAuthor());
default:
return pht(
'%s requested review of this revision.',
$this->renderAuthor());
}
}
public function getTitleForFeed() {
return pht(
'%s requested review of %s.',
$this->renderAuthor(),
$this->renderObject());
$source = $this->getDraftPromotionSource();
switch ($source) {
case self::SOURCE_HARBORMASTER:
case self::SOURCE_VIEWER:
case self::SOURCE_AUTHOR:
return pht(
'%s published %s for review.',
$this->renderAuthor(),
$this->renderObject());
default:
return pht(
'%s requested review of %s.',
$this->renderAuthor(),
$this->renderObject());
}
}
public function getTransactionTypeForConduit($xaction) {
@@ -105,4 +161,36 @@ final class DifferentialRevisionRequestReviewTransaction
return array();
}
private function getDraftPromotionSource() {
return $this->getMetadataValue('promotion.source');
}
private function getActorSourceType(
DifferentialRevision $revision,
PhabricatorUser $viewer) {
$is_harbormaster = $viewer->isOmnipotent();
$is_author = $this->isViewerRevisionAuthor($revision, $viewer);
$is_draft = $revision->isDraft();
if ($is_harbormaster) {
// When revisions automatically promote out of "Draft" after builds
// finish, the viewer may be acting as the Harbormaster application.
$source = self::SOURCE_HARBORMASTER;
} else if ($is_author) {
$source = self::SOURCE_AUTHOR;
} else if ($is_draft) {
// Non-authors are allowed to "Request Review" on draft revisions, to
// force them into review immediately.
$source = self::SOURCE_VIEWER;
} else {
throw new Exception(
pht(
'You can not request review of this revision because you are not '.
'the author of the revision and it is not currently a draft.'));
}
return $source;
}
}

View File

@@ -6,12 +6,15 @@ final class DifferentialRevisionResignTransaction
const TRANSACTIONTYPE = 'differential.revision.resign';
const ACTIONKEY = 'resign';
protected function getRevisionActionLabel() {
protected function getRevisionActionLabel(
DifferentialRevision $revision,
PhabricatorUser $viewer) {
return pht('Resign as Reviewer');
}
protected function getRevisionActionDescription(
DifferentialRevision $revision) {
DifferentialRevision $revision,
PhabricatorUser $viewer) {
return pht('You will resign as a reviewer for this change.');
}

View File

@@ -52,7 +52,6 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication {
'change/(?P<dblob>.*)' => 'DiffusionChangeController',
'clone/' => 'DiffusionCloneController',
'history/(?P<dblob>.*)' => 'DiffusionHistoryController',
'graph/(?P<dblob>.*)' => 'DiffusionGraphController',
'browse/(?P<dblob>.*)' => 'DiffusionBrowseController',
'document/(?P<dblob>.*)'
=> 'DiffusionDocumentController',

View File

@@ -0,0 +1,58 @@
<?php
final class DiffusionInternalCommitSearchConduitAPIMethod
extends PhabricatorSearchEngineAPIMethod {
public function getAPIMethodName() {
return 'internal.commit.search';
}
public function newSearchEngine() {
return new DiffusionInternalCommitSearchEngine();
}
public function getMethodSummary() {
return pht('Read raw information about commits.');
}
protected function newConduitCallProxyClient(ConduitAPIRequest $request) {
$viewer = $request->getViewer();
$constraints = $request->getValue('constraints');
if (is_array($constraints)) {
$repository_phids = idx($constraints, 'repositoryPHIDs');
} else {
$repository_phids = array();
}
$repository_phid = null;
if (is_array($repository_phids)) {
if (phutil_is_natural_list($repository_phids)) {
if (count($repository_phids) === 1) {
$value = head($repository_phids);
if (is_string($value)) {
$repository_phid = $value;
}
}
}
}
if ($repository_phid === null) {
throw new Exception(
pht(
'This internal method must be invoked with a "repositoryPHIDs" '.
'constraint with exactly one value.'));
}
$repository = id(new PhabricatorRepositoryQuery())
->setViewer($viewer)
->withPHIDs(array($repository_phid))
->executeOne();
if (!$repository) {
return array();
}
return $repository->newConduitClientForRequest($request);
}
}

View File

@@ -38,7 +38,6 @@ final class DiffusionQueryCommitsConduitAPIMethod
protected function execute(ConduitAPIRequest $request) {
$need_messages = $request->getValue('needMessages');
$bypass_cache = $request->getValue('bypassCache');
$viewer = $request->getUser();
$query = id(new DiffusionCommitQuery())
@@ -53,12 +52,6 @@ final class DiffusionQueryCommitsConduitAPIMethod
->executeOne();
if ($repository) {
$query->withRepository($repository);
if ($bypass_cache) {
id(new DiffusionRepositoryClusterEngine())
->setViewer($viewer)
->setRepository($repository)
->synchronizeWorkingCopyBeforeRead();
}
}
}
@@ -96,48 +89,22 @@ final class DiffusionQueryCommitsConduitAPIMethod
'repositoryPHID' => $commit->getRepository()->getPHID(),
'identifier' => $commit->getCommitIdentifier(),
'epoch' => $commit->getEpoch(),
'authorEpoch' => $commit_data->getCommitDetail('authorEpoch'),
'authorEpoch' => $commit_data->getAuthorEpoch(),
'uri' => $uri,
'isImporting' => !$commit->isImported(),
'summary' => $commit->getSummary(),
'authorPHID' => $commit->getAuthorPHID(),
'committerPHID' => $commit_data->getCommitDetail('committerPHID'),
'author' => $commit_data->getAuthorName(),
'authorName' => $commit_data->getCommitDetail('authorName'),
'authorEmail' => $commit_data->getCommitDetail('authorEmail'),
'committer' => $commit_data->getCommitDetail('committer'),
'committerName' => $commit_data->getCommitDetail('committerName'),
'committerEmail' => $commit_data->getCommitDetail('committerEmail'),
'author' => $commit_data->getAuthorString(),
'authorName' => $commit_data->getAuthorDisplayName(),
'authorEmail' => $commit_data->getAuthorEmail(),
'committer' => $commit_data->getCommitterString(),
'committerName' => $commit_data->getCommitterDisplayName(),
'committerEmail' => $commit_data->getCommitterEmail(),
'hashes' => array(),
);
if ($bypass_cache) {
$lowlevel_commitref = id(new DiffusionLowLevelCommitQuery())
->setRepository($commit->getRepository())
->withIdentifier($commit->getCommitIdentifier())
->execute();
$dict['authorEpoch'] = $lowlevel_commitref->getAuthorEpoch();
$dict['author'] = $lowlevel_commitref->getAuthor();
$dict['authorName'] = $lowlevel_commitref->getAuthorName();
$dict['authorEmail'] = $lowlevel_commitref->getAuthorEmail();
$dict['committer'] = $lowlevel_commitref->getCommitter();
$dict['committerName'] = $lowlevel_commitref->getCommitterName();
$dict['committerEmail'] = $lowlevel_commitref->getCommitterEmail();
if ($need_messages) {
$dict['message'] = $lowlevel_commitref->getMessage();
}
foreach ($lowlevel_commitref->getHashes() as $hash) {
$dict['hashes'][] = array(
'type' => $hash->getHashType(),
'value' => $hash->getHashValue(),
);
}
}
if ($need_messages && !$bypass_cache) {
if ($need_messages) {
$dict['message'] = $commit_data->getCommitMessage();
}

View File

@@ -85,6 +85,35 @@ abstract class DiffusionQueryConduitAPIMethod
* should occur after @{method:getResult}, like formatting a timestamp.
*/
final protected function execute(ConduitAPIRequest $request) {
$drequest = $this->getDiffusionRequest();
// We pass this flag on to prevent proxying of any other Conduit calls
// which we need to make in order to respond to this one. Although we
// could safely proxy them, we take a big performance hit in the common
// case, and doing more proxying wouldn't exercise any additional code so
// we wouldn't gain a testability/predictability benefit.
$is_cluster_request = $request->getIsClusterRequest();
$drequest->setIsClusterRequest($is_cluster_request);
$viewer = $request->getViewer();
$repository = $drequest->getRepository();
// TODO: Allow web UI queries opt out of this if they don't care about
// fetching the most up-to-date data? Synchronization can be slow, and a
// lot of web reads are probably fine if they're a few seconds out of
// date.
id(new DiffusionRepositoryClusterEngine())
->setViewer($viewer)
->setRepository($repository)
->synchronizeWorkingCopyBeforeRead();
return $this->getResult($request);
}
protected function newConduitCallProxyClient(ConduitAPIRequest $request) {
$viewer = $request->getViewer();
$identifier = $request->getValue('repository');
if ($identifier === null) {
$identifier = $request->getValue('callsign');
@@ -92,7 +121,7 @@ abstract class DiffusionQueryConduitAPIMethod
$drequest = DiffusionRequest::newFromDictionary(
array(
'user' => $request->getUser(),
'user' => $viewer,
'repository' => $identifier,
'branch' => $request->getValue('branch'),
'path' => $request->getValue('path'),
@@ -106,46 +135,16 @@ abstract class DiffusionQueryConduitAPIMethod
$identifier));
}
// Figure out whether we're going to handle this request on this device,
// or proxy it to another node in the cluster.
// If this is a cluster request and we need to proxy, we'll explode here
// to prevent infinite recursion.
$is_cluster_request = $request->getIsClusterRequest();
$viewer = $request->getUser();
$repository = $drequest->getRepository();
$client = $repository->newConduitClient(
$viewer,
$is_cluster_request);
$client = $repository->newConduitClientForRequest($request);
if ($client) {
// We're proxying, so just make an intracluster call.
return $client->callMethodSynchronous(
$this->getAPIMethodName(),
$request->getAllParameters());
} else {
// We pass this flag on to prevent proxying of any other Conduit calls
// which we need to make in order to respond to this one. Although we
// could safely proxy them, we take a big performance hit in the common
// case, and doing more proxying wouldn't exercise any additional code so
// we wouldn't gain a testability/predictability benefit.
$drequest->setIsClusterRequest($is_cluster_request);
$this->setDiffusionRequest($drequest);
// TODO: Allow web UI queries opt out of this if they don't care about
// fetching the most up-to-date data? Synchronization can be slow, and a
// lot of web reads are probably fine if they're a few seconds out of
// date.
id(new DiffusionRepositoryClusterEngine())
->setViewer($viewer)
->setRepository($repository)
->synchronizeWorkingCopyBeforeRead();
return $this->getResult($request);
return $client;
}
$this->setDiffusionRequest($drequest);
return null;
}
protected function getResult(ConduitAPIRequest $request) {

View File

@@ -292,7 +292,6 @@ final class DiffusionBrowseController extends DiffusionController {
$empty_result = null;
$browse_panel = null;
$branch_panel = null;
if (!$results->isValidResults()) {
$empty_result = new DiffusionEmptyResultView();
$empty_result->setDiffusionRequest($drequest);
@@ -328,12 +327,6 @@ final class DiffusionBrowseController extends DiffusionController {
->setTable($browse_table)
->addClass('diffusion-mobile-view')
->setPager($pager);
$path = $drequest->getPath();
$is_branch = (!strlen($path) && $repository->supportsBranchComparison());
if ($is_branch) {
$branch_panel = $this->buildBranchTable();
}
}
$open_revisions = $this->buildOpenRevisions();
@@ -359,7 +352,6 @@ final class DiffusionBrowseController extends DiffusionController {
->setFooter(
array(
$bar,
$branch_panel,
$empty_result,
$browse_panel,
$open_revisions,
@@ -1074,59 +1066,4 @@ final class DiffusionBrowseController extends DiffusionController {
return $file;
}
private function buildBranchTable() {
$viewer = $this->getViewer();
$drequest = $this->getDiffusionRequest();
$repository = $drequest->getRepository();
$branch = $drequest->getBranch();
$default_branch = $repository->getDefaultBranch();
if ($branch === $default_branch) {
return null;
}
$pager = id(new PHUIPagerView())
->setPageSize(10);
try {
$results = $this->callConduitWithDiffusionRequest(
'diffusion.historyquery',
array(
'commit' => $branch,
'against' => $default_branch,
'path' => $drequest->getPath(),
'offset' => $pager->getOffset(),
'limit' => $pager->getPageSize() + 1,
));
} catch (Exception $ex) {
return null;
}
$history = DiffusionPathChange::newFromConduit($results['pathChanges']);
$history = $pager->sliceResults($history);
if (!$history) {
return null;
}
$history_table = id(new DiffusionHistoryTableView())
->setViewer($viewer)
->setDiffusionRequest($drequest)
->setHistory($history)
->setParents($results['parents'])
->setFilterParents(true)
->setIsHead(true)
->setIsTail(!$pager->getHasMorePages());
$header = id(new PHUIHeaderView())
->setHeader(pht('%s vs %s', $branch, $default_branch));
return id(new PHUIObjectBoxView())
->setHeader($header)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->addClass('diffusion-mobile-view')
->setTable($history_table);
}
}

View File

@@ -90,10 +90,9 @@ final class DiffusionCommitController extends DiffusionController {
->setSeverity(PHUIInfoView::SEVERITY_WARNING)
->appendChild($warning_message);
$list = id(new DiffusionCommitListView())
$list = id(new DiffusionCommitGraphView())
->setViewer($viewer)
->setCommits($commits)
->setNoDataString(pht('No recent commits.'));
->setCommits($commits);
$crumbs->addTextCrumb(pht('Ambiguous Commit'));
@@ -183,7 +182,6 @@ final class DiffusionCommitController extends DiffusionController {
}
$curtain = $this->buildCurtain($commit, $repository);
$subheader = $this->buildSubheaderView($commit, $commit_data);
$details = $this->buildPropertyListView(
$commit,
$commit_data,
@@ -483,7 +481,6 @@ final class DiffusionCommitController extends DiffusionController {
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setSubheader($subheader)
->setCurtain($curtain)
->setMainColumn(
array(
@@ -625,33 +622,50 @@ final class DiffusionCommitController extends DiffusionController {
}
}
$author_epoch = $data->getCommitDetail('authorEpoch');
$provenance_list = new PHUIStatusListView();
$committed_info = id(new PHUIStatusItemView())
->setNote(phabricator_datetime($commit->getEpoch(), $viewer))
->setTarget($commit->renderAnyCommitter($viewer, $handles));
$author_view = $commit->newCommitAuthorView($viewer);
if ($author_view) {
$author_date = $data->getAuthorEpoch();
$author_date = phabricator_datetime($author_date, $viewer);
$committed_list = new PHUIStatusListView();
$committed_list->addItem($committed_info);
$view->addProperty(
pht('Committed'),
$committed_list);
$provenance_list->addItem(
id(new PHUIStatusItemView())
->setTarget($author_view)
->setNote(pht('Authored on %s', $author_date)));
}
if (!$commit->isAuthorSameAsCommitter()) {
$committer_view = $commit->newCommitCommitterView($viewer);
if ($committer_view) {
$committer_date = $commit->getEpoch();
$committer_date = phabricator_datetime($committer_date, $viewer);
$provenance_list->addItem(
id(new PHUIStatusItemView())
->setTarget($committer_view)
->setNote(pht('Committed on %s', $committer_date)));
}
}
if ($push_logs) {
$pushed_list = new PHUIStatusListView();
foreach ($push_logs as $push_log) {
$pushed_item = id(new PHUIStatusItemView())
->setTarget($handles[$push_log->getPusherPHID()]->renderLink())
->setNote(phabricator_datetime($push_log->getEpoch(), $viewer));
$pushed_list->addItem($pushed_item);
}
$pusher_date = $push_log->getEpoch();
$pusher_date = phabricator_datetime($pusher_date, $viewer);
$view->addProperty(
pht('Pushed'),
$pushed_list);
$pusher_view = $handles[$push_log->getPusherPHID()]->renderLink();
$provenance_list->addItem(
id(new PHUIStatusItemView())
->setTarget($pusher_view)
->setNote(pht('Pushed on %s', $pusher_date)));
}
}
$view->addProperty(pht('Provenance'), $provenance_list);
$reviewer_phid = $data->getCommitDetail('reviewerPHID');
if ($reviewer_phid) {
$view->addProperty(
@@ -743,52 +757,6 @@ final class DiffusionCommitController extends DiffusionController {
return $view;
}
private function buildSubheaderView(
PhabricatorRepositoryCommit $commit,
PhabricatorRepositoryCommitData $data) {
$viewer = $this->getViewer();
$drequest = $this->getDiffusionRequest();
$repository = $drequest->getRepository();
if ($repository->isSVN()) {
return null;
}
$author_phid = $commit->getAuthorDisplayPHID();
$author_name = $data->getAuthorName();
$author_epoch = $data->getCommitDetail('authorEpoch');
$date = null;
if ($author_epoch !== null) {
$date = phabricator_datetime($author_epoch, $viewer);
}
if ($author_phid) {
$handles = $viewer->loadHandles(array($author_phid));
$image_uri = $handles[$author_phid]->getImageURI();
$image_href = $handles[$author_phid]->getURI();
$author = $handles[$author_phid]->renderLink();
} else if (strlen($author_name)) {
$author = $author_name;
$image_uri = null;
$image_href = null;
} else {
return null;
}
$author = phutil_tag('strong', array(), $author);
if ($date) {
$content = pht('Authored by %s on %s.', $author, $date);
} else {
$content = pht('Authored by %s.', $author);
}
return id(new PHUIHeadThingView())
->setImage($image_uri)
->setImageHref($image_href)
->setContent($content);
}
private function buildComments(PhabricatorRepositoryCommit $commit) {
$timeline = $this->buildTransactionTimeline(
$commit,
@@ -843,15 +811,15 @@ final class DiffusionCommitController extends DiffusionController {
new PhutilNumber($limit)));
}
$history_table = id(new DiffusionHistoryTableView())
->setUser($viewer)
$commit_list = id(new DiffusionCommitGraphView())
->setViewer($viewer)
->setDiffusionRequest($drequest)
->setHistory($merges);
$panel = id(new PHUIObjectBoxView())
->setHeaderText(pht('Merged Changes'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setTable($history_table);
->setObjectList($commit_list->newObjectItemListView());
if ($caption) {
$panel->setInfoView($caption);
}

View File

@@ -285,7 +285,6 @@ final class DiffusionCompareController extends DiffusionController {
$request = $this->getRequest();
$viewer = $this->getViewer();
$drequest = $this->getDiffusionRequest();
$repository = $drequest->getRepository();
if (!$history) {
return $this->renderStatusMessage(
@@ -296,8 +295,8 @@ final class DiffusionCompareController extends DiffusionController {
phutil_tag('strong', array(), $against_ref)));
}
$history_table = id(new DiffusionHistoryTableView())
->setUser($viewer)
$history_view = id(new DiffusionCommitGraphView())
->setViewer($viewer)
->setDiffusionRequest($drequest)
->setHistory($history)
->setParents($results['parents'])
@@ -305,15 +304,6 @@ final class DiffusionCompareController extends DiffusionController {
->setIsHead(!$pager->getOffset())
->setIsTail(!$pager->getHasMorePages());
$header = id(new PHUIHeaderView())
->setHeader(pht('Commits'));
return id(new PHUIObjectBoxView())
->setHeader($header)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setTable($history_table)
->addClass('diffusion-mobile-view')
->setPager($pager);
return $history_view;
}
}

View File

@@ -210,9 +210,6 @@ abstract class DiffusionController extends PhabricatorController {
case 'history':
$view_name = pht('History');
break;
case 'graph':
$view_name = pht('Graph');
break;
case 'browse':
$view_name = pht('Browse');
break;
@@ -553,17 +550,6 @@ abstract class DiffusionController extends PhabricatorController {
)))
->setSelected($key == 'history'));
$view->addMenuItem(
id(new PHUIListItemView())
->setKey('graph')
->setName(pht('Graph'))
->setIcon('fa-code-fork')
->setHref($drequest->generateURI(
array(
'action' => 'graph',
)))
->setSelected($key == 'graph'));
return $view;
}

View File

@@ -1,110 +0,0 @@
<?php
final class DiffusionGraphController extends DiffusionController {
public function shouldAllowPublic() {
return true;
}
public function handleRequest(AphrontRequest $request) {
$response = $this->loadDiffusionContext();
if ($response) {
return $response;
}
require_celerity_resource('diffusion-css');
$viewer = $this->getViewer();
$drequest = $this->getDiffusionRequest();
$repository = $drequest->getRepository();
$pager = id(new PHUIPagerView())
->readFromRequest($request);
$params = array(
'commit' => $drequest->getCommit(),
'path' => $drequest->getPath(),
'offset' => $pager->getOffset(),
'limit' => $pager->getPageSize() + 1,
);
$history_results = $this->callConduitWithDiffusionRequest(
'diffusion.historyquery',
$params);
$history = DiffusionPathChange::newFromConduit(
$history_results['pathChanges']);
$history = $pager->sliceResults($history);
$graph = id(new DiffusionHistoryTableView())
->setViewer($viewer)
->setDiffusionRequest($drequest)
->setHistory($history);
$show_graph = !strlen($drequest->getPath());
if ($show_graph) {
$graph->setParents($history_results['parents']);
$graph->setIsHead(!$pager->getOffset());
$graph->setIsTail(!$pager->getHasMorePages());
}
$header = $this->buildHeader($drequest);
$crumbs = $this->buildCrumbs(
array(
'branch' => true,
'path' => true,
'view' => 'graph',
));
$crumbs->setBorder(true);
$title = array(
pht('Graph'),
$repository->getDisplayName(),
);
$graph_view = id(new PHUIObjectBoxView())
->setHeaderText(pht('History Graph'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setTable($graph)
->addClass('diffusion-mobile-view')
->setPager($pager);
$tabs = $this->buildTabsView('graph');
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setTabs($tabs)
->setFooter($graph_view);
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild($view);
}
private function buildHeader(DiffusionRequest $drequest) {
$viewer = $this->getViewer();
$repository = $drequest->getRepository();
$no_path = !strlen($drequest->getPath());
if ($no_path) {
$header_text = pht('Graph');
} else {
$header_text = $this->renderPathLinks($drequest, $mode = 'history');
}
$header = id(new PHUIHeaderView())
->setUser($viewer)
->setHeader($header_text)
->setHeaderIcon('fa-code-fork');
if (!$repository->isSVN()) {
$branch_tag = $this->renderBranchTag($drequest);
$header->addTag($branch_tag);
}
return $header;
}
}

View File

@@ -35,11 +35,29 @@ final class DiffusionHistoryController extends DiffusionController {
$history = $pager->sliceResults($history);
$history_list = id(new DiffusionHistoryListView())
$history_list = id(new DiffusionCommitGraphView())
->setViewer($viewer)
->setDiffusionRequest($drequest)
->setHistory($history);
// NOTE: If we have a path (like "src/"), many nodes in the graph are
// likely to be missing (since the path wasn't touched by those commits).
// If we draw the graph, commits will often appear to be unrelated because
// intermediate nodes are omitted. Just drop the graph.
// The ideal behavior would be to load the entire graph and then connect
// ancestors appropriately, but this would currrently be prohibitively
// expensive in the general case.
$show_graph = !strlen($drequest->getPath());
if ($show_graph) {
$history_list
->setParents($history_results['parents'])
->setIsHead(!$pager->getOffset())
->setIsTail(!$pager->getHasMorePages());
}
$header = $this->buildHeader($drequest);
$crumbs = $this->buildCrumbs(

View File

@@ -53,14 +53,6 @@ final class DiffusionLastModifiedController extends DiffusionController {
}
}
$phids = array();
foreach ($commits as $commit) {
$phids[] = $commit->getCommitterDisplayPHID();
$phids[] = $commit->getAuthorDisplayPHID();
}
$phids = array_filter($phids);
$handles = $this->loadViewerHandles($phids);
$branch = $drequest->loadBranch();
if ($branch && $commits) {
$lint_query = id(new DiffusionLintCountQuery())
@@ -83,7 +75,6 @@ final class DiffusionLastModifiedController extends DiffusionController {
$output[$path] = $this->renderColumns(
$prequest,
$handles,
$commit,
idx($lint, $path));
}
@@ -93,11 +84,9 @@ final class DiffusionLastModifiedController extends DiffusionController {
private function renderColumns(
DiffusionRequest $drequest,
array $handles,
PhabricatorRepositoryCommit $commit = null,
$lint = null) {
assert_instances_of($handles, 'PhabricatorObjectHandle');
$viewer = $this->getRequest()->getUser();
$viewer = $this->getViewer();
if ($commit) {
$epoch = $commit->getEpoch();
@@ -110,13 +99,6 @@ final class DiffusionLastModifiedController extends DiffusionController {
$date = '';
}
$author = $commit->renderAuthor($viewer, $handles);
$committer = $commit->renderCommitter($viewer, $handles);
if ($author != $committer) {
$author = hsprintf('%s/%s', $author, $committer);
}
$data = $commit->getCommitData();
$details = DiffusionView::linkDetail(
$drequest->getRepository(),
@@ -124,11 +106,9 @@ final class DiffusionLastModifiedController extends DiffusionController {
$data->getSummary());
$details = AphrontTableView::renderSingleDisplayLine($details);
$return = array(
'commit' => $modified,
'date' => $date,
'author' => $author,
'details' => $details,
);

View File

@@ -299,16 +299,10 @@ final class DiffusionRepositoryController extends DiffusionController {
$handles,
$browse_pager);
$content[] = $this->buildHistoryTable(
$history_results,
$history,
$history_exception);
if ($readme) {
$content[] = $readme;
}
try {
$branch_button = $this->buildBranchList($drequest);
$this->branchButton = $branch_button;
@@ -428,51 +422,6 @@ final class DiffusionRepositoryController extends DiffusionController {
return null;
}
private function buildHistoryTable(
$history_results,
$history,
$history_exception) {
$request = $this->getRequest();
$viewer = $request->getUser();
$drequest = $this->getDiffusionRequest();
$repository = $drequest->getRepository();
if ($history_exception) {
if ($repository->isImporting()) {
return $this->renderStatusMessage(
pht('Still Importing...'),
pht(
'This repository is still importing. History is not yet '.
'available.'));
} else {
return $this->renderStatusMessage(
pht('Unable to Retrieve History'),
$history_exception->getMessage());
}
}
$history_table = id(new DiffusionHistoryTableView())
->setUser($viewer)
->setDiffusionRequest($drequest)
->setHistory($history)
->setIsHead(true);
if ($history_results) {
$history_table->setParents($history_results['parents']);
}
$panel = id(new PHUIObjectBoxView())
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->addClass('diffusion-mobile-view');
$header = id(new PHUIHeaderView())
->setHeader(pht('Recent Commits'));
$panel->setHeader($header);
$panel->setTable($history_table);
return $panel;
}
private function buildBranchList(DiffusionRequest $drequest) {
$viewer = $this->getViewer();

View File

@@ -922,18 +922,26 @@ final class DiffusionServeController extends DiffusionController {
// This is a pretty funky fix: it would be nice to more precisely detect
// that a request is a `--depth N` clone request, but we don't have any code
// to decode protocol frames yet. Instead, look for reasonable evidence
// in the error and output that we're looking at a `--depth` clone.
// in the output that we're looking at a `--depth` clone.
// For evidence this isn't completely crazy, see:
// https://github.com/schacon/grack/pull/7
// A valid x-git-upload-pack-result response during packfile negotiation
// should end with a flush packet ("0000"). As long as that packet
// terminates the response body in the response, we'll assume the response
// is correct and complete.
// See https://git-scm.com/docs/pack-protocol#_packfile_negotiation
$stdout_regexp = '(^Content-Type: application/x-git-upload-pack-result)m';
$stderr_regexp = '(The remote end hung up unexpectedly)';
$has_pack = preg_match($stdout_regexp, $stdout);
$is_hangup = preg_match($stderr_regexp, $stderr);
return $has_pack && $is_hangup;
if (strlen($stdout) >= 4) {
$has_flush_packet = (substr($stdout, -4) === "0000");
} else {
$has_flush_packet = false;
}
return ($has_pack && $has_flush_packet);
}
private function getCommonEnvironment(PhabricatorUser $viewer) {

View File

@@ -34,4 +34,21 @@ final class DiffusionCommitHash extends Phobject {
}
return $hash_objects;
}
public static function newFromDictionary(array $map) {
$hash_type = idx($map, 'type');
$hash_value = idx($map, 'value');
return id(new self())
->setHashType($hash_type)
->setHashValue($hash_value);
}
public function newDictionary() {
return array(
'type' => $this->hashType,
'value' => $this->hashValue,
);
}
}

View File

@@ -10,28 +10,48 @@ final class DiffusionCommitRef extends Phobject {
private $committerEmail;
private $hashes = array();
public static function newFromConduitResult(array $result) {
$ref = id(new DiffusionCommitRef())
->setAuthorEpoch(idx($result, 'authorEpoch'))
->setCommitterEmail(idx($result, 'committerEmail'))
->setCommitterName(idx($result, 'committerName'))
->setAuthorEmail(idx($result, 'authorEmail'))
->setAuthorName(idx($result, 'authorName'))
->setMessage(idx($result, 'message'));
public function newDictionary() {
$hashes = $this->getHashes();
$hashes = mpull($hashes, 'newDictionary');
$hashes = array_values($hashes);
$hashes = array();
foreach (idx($result, 'hashes', array()) as $hash_result) {
$hashes[] = id(new DiffusionCommitHash())
->setHashType(idx($hash_result, 'type'))
->setHashValue(idx($hash_result, 'value'));
return array(
'authorEpoch' => $this->authorEpoch,
'authorName' => $this->authorName,
'authorEmail' => $this->authorEmail,
'committerName' => $this->committerName,
'committerEmail' => $this->committerEmail,
'message' => $this->message,
'hashes' => $hashes,
);
}
public static function newFromDictionary(array $map) {
$hashes = idx($map, 'hashes', array());
foreach ($hashes as $key => $hash_map) {
$hashes[$key] = DiffusionCommitHash::newFromDictionary($hash_map);
}
$hashes = array_values($hashes);
$ref->setHashes($hashes);
$author_epoch = idx($map, 'authorEpoch');
$author_name = idx($map, 'authorName');
$author_email = idx($map, 'authorEmail');
$committer_name = idx($map, 'committerName');
$committer_email = idx($map, 'committerEmail');
$message = idx($map, 'message');
return $ref;
return id(new self())
->setAuthorEpoch($author_epoch)
->setAuthorName($author_name)
->setAuthorEmail($author_email)
->setCommitterName($committer_name)
->setCommitterEmail($committer_email)
->setMessage($message)
->setHashes($hashes);
}
public function setHashes(array $hashes) {
assert_instances_of($hashes, 'DiffusionCommitHash');
$this->hashes = $hashes;
return $this;
}

View File

@@ -104,7 +104,7 @@ final class DiffusionPathChange extends Phobject {
public function getAuthorName() {
if ($this->getCommitData()) {
return $this->getCommitData()->getAuthorName();
return $this->getCommitData()->getAuthorString();
}
return null;
}

View File

@@ -68,10 +68,15 @@ final class DiffusionMercurialCommandEngine
// http://selenic.com/pipermail/mercurial-devel/2011-February/028541.html
//
// After Jan 2015, it may also fail to write to a revision branch cache.
//
// Separately, it may fail to write to a different branch cache, and may
// encounter issues reading the branch cache.
$ignore = array(
'ignoring untrusted configuration option',
"couldn't write revision branch cache:",
"couldn't write branch cache:",
'invalid branchheads cache',
);
foreach ($ignore as $key => $pattern) {

View File

@@ -73,17 +73,11 @@ abstract class DiffusionQuery extends PhabricatorQuery {
$params = $params + $core_params;
$client = $repository->newConduitClient(
$future = $repository->newConduitFuture(
$user,
$method,
$params,
$drequest->getIsClusterRequest());
if (!$client) {
$result = id(new ConduitCall($method, $params))
->setUser($user)
->execute();
$future = new ImmediateFuture($result);
} else {
$future = $client->callMethod($method, $params);
}
if (!$return_future) {
return $future->resolve();

View File

@@ -76,7 +76,6 @@ final class DiffusionBrowseTableView extends DiffusionView {
$dict = array(
'lint' => celerity_generate_unique_node_id(),
'date' => celerity_generate_unique_node_id(),
'author' => celerity_generate_unique_node_id(),
'details' => celerity_generate_unique_node_id(),
);

View File

@@ -0,0 +1,642 @@
<?php
final class DiffusionCommitGraphView
extends DiffusionView {
private $history;
private $commits;
private $isHead;
private $isTail;
private $parents;
private $filterParents;
private $commitMap;
private $buildableMap;
private $revisionMap;
private $showAuditors;
public function setHistory(array $history) {
assert_instances_of($history, 'DiffusionPathChange');
$this->history = $history;
return $this;
}
public function getHistory() {
return $this->history;
}
public function setCommits(array $commits) {
assert_instances_of($commits, 'PhabricatorRepositoryCommit');
$this->commits = $commits;
return $this;
}
public function getCommits() {
return $this->commits;
}
public function setShowAuditors($show_auditors) {
$this->showAuditors = $show_auditors;
return $this;
}
public function getShowAuditors() {
return $this->showAuditors;
}
public function setParents(array $parents) {
$this->parents = $parents;
return $this;
}
public function getParents() {
return $this->parents;
}
public function setIsHead($is_head) {
$this->isHead = $is_head;
return $this;
}
public function getIsHead() {
return $this->isHead;
}
public function setIsTail($is_tail) {
$this->isTail = $is_tail;
return $this;
}
public function getIsTail() {
return $this->isTail;
}
public function setFilterParents($filter_parents) {
$this->filterParents = $filter_parents;
return $this;
}
public function getFilterParents() {
return $this->filterParents;
}
private function getRepository() {
$drequest = $this->getDiffusionRequest();
if (!$drequest) {
return null;
}
return $drequest->getRepository();
}
public function newObjectItemListView() {
$list_view = id(new PHUIObjectItemListView());
$item_views = $this->newObjectItemViews();
foreach ($item_views as $item_view) {
$list_view->addItem($item_view);
}
return $list_view;
}
private function newObjectItemViews() {
$viewer = $this->getViewer();
require_celerity_resource('diffusion-css');
$show_builds = $this->shouldShowBuilds();
$show_revisions = $this->shouldShowRevisions();
$show_auditors = $this->shouldShowAuditors();
$phids = array();
if ($show_revisions) {
$revision_map = $this->getRevisionMap();
foreach ($revision_map as $revisions) {
foreach ($revisions as $revision) {
$phids[] = $revision->getPHID();
}
}
}
$commits = $this->getCommitMap();
foreach ($commits as $commit) {
$author_phid = $commit->getAuthorDisplayPHID();
if ($author_phid !== null) {
$phids[] = $author_phid;
}
}
if ($show_auditors) {
foreach ($commits as $commit) {
$audits = $commit->getAudits();
foreach ($audits as $auditor) {
$phids[] = $auditor->getAuditorPHID();
}
}
}
$handles = $viewer->loadHandles($phids);
$views = array();
$items = $this->newHistoryItems();
foreach ($items as $hash => $item) {
$content = array();
$commit = $item['commit'];
$commit_description = $this->getCommitDescription($commit);
$commit_link = $this->getCommitURI($hash);
$short_hash = $this->getCommitObjectName($hash);
$is_disabled = $this->getCommitIsDisabled($commit);
$item_view = id(new PHUIObjectItemView())
->setViewer($viewer)
->setHeader($commit_description)
->setObjectName($short_hash)
->setHref($commit_link)
->setDisabled($is_disabled);
$this->addBrowseAction($item_view, $hash);
if ($show_builds) {
$this->addBuildAction($item_view, $hash);
}
$this->addAuditAction($item_view, $hash);
if ($show_auditors) {
$auditor_list = $item_view->newMapView();
if ($commit) {
$auditors = $this->newAuditorList($commit, $handles);
$auditor_list->newItem()
->setName(pht('Auditors'))
->setValue($auditors);
}
}
$property_list = $item_view->newMapView();
if ($commit) {
$author_view = $this->getCommitAuthorView($commit);
if ($author_view) {
$property_list->newItem()
->setName(pht('Author'))
->setValue($author_view);
}
}
if ($show_revisions) {
if ($commit) {
$revisions = $this->getRevisions($commit);
if ($revisions) {
$list_view = $handles->newSublist(mpull($revisions, 'getPHID'))
->newListView();
$property_list->newItem()
->setName(pht('Revisions'))
->setValue($list_view);
}
}
}
$views[$hash] = $item_view;
}
return $views;
}
private function newObjectItemRows() {
$viewer = $this->getViewer();
$items = $this->newHistoryItems();
$views = $this->newObjectItemViews();
$last_date = null;
$rows = array();
foreach ($items as $hash => $item) {
$item_epoch = $item['epoch'];
$item_date = phabricator_date($item_epoch, $viewer);
if ($item_date !== $last_date) {
$last_date = $item_date;
$header = $item_date;
} else {
$header = null;
}
$item_view = $views[$hash];
$list_view = id(new PHUIObjectItemListView())
->setViewer($viewer)
->setFlush(true)
->addItem($item_view);
if ($header !== null) {
$list_view->setHeader($header);
}
$rows[] = $list_view;
}
return $rows;
}
public function render() {
$rows = $this->newObjectItemRows();
$graph = $this->newGraphView();
foreach ($rows as $idx => $row) {
$cells = array();
if ($graph) {
$cells[] = phutil_tag(
'td',
array(
'class' => 'diffusion-commit-graph-path-cell',
),
$graph[$idx]);
}
$cells[] = phutil_tag(
'td',
array(
'class' => 'diffusion-commit-graph-content-cell',
),
$row);
$rows[$idx] = phutil_tag('tr', array(), $cells);
}
$table = phutil_tag(
'table',
array(
'class' => 'diffusion-commit-graph-table',
),
$rows);
return $table;
}
private function newGraphView() {
if (!$this->getParents()) {
return null;
}
$parents = $this->getParents();
// If we're filtering parents, remove relationships which point to
// commits that are not part of the visible graph. Otherwise, we get
// a big tree of nonsense when viewing release branches like "stable"
// versus "master".
if ($this->getFilterParents()) {
foreach ($parents as $key => $nodes) {
foreach ($nodes as $nkey => $node) {
if (empty($parents[$node])) {
unset($parents[$key][$nkey]);
}
}
}
}
return id(new PHUIDiffGraphView())
->setIsHead($this->getIsHead())
->setIsTail($this->getIsTail())
->renderGraph($parents);
}
private function shouldShowBuilds() {
$viewer = $this->getViewer();
$show_builds = PhabricatorApplication::isClassInstalledForViewer(
'PhabricatorHarbormasterApplication',
$this->getUser());
return $show_builds;
}
private function shouldShowRevisions() {
$viewer = $this->getViewer();
$show_revisions = PhabricatorApplication::isClassInstalledForViewer(
'PhabricatorDifferentialApplication',
$viewer);
return $show_revisions;
}
private function shouldShowAuditors() {
return $this->getShowAuditors();
}
private function newHistoryItems() {
$items = array();
$history = $this->getHistory();
if ($history !== null) {
foreach ($history as $history_item) {
$commit_hash = $history_item->getCommitIdentifier();
$items[$commit_hash] = array(
'epoch' => $history_item->getEpoch(),
'hash' => $commit_hash,
'commit' => $this->getCommit($commit_hash),
);
}
} else {
$commits = $this->getCommitMap();
foreach ($commits as $commit) {
$commit_hash = $commit->getCommitIdentifier();
$items[$commit_hash] = array(
'epoch' => $commit->getEpoch(),
'hash' => $commit_hash,
'commit' => $commit,
);
}
}
return $items;
}
private function getCommitDescription($commit) {
if (!$commit) {
return phutil_tag('em', array(), pht("Discovering\xE2\x80\xA6"));
}
// We can show details once the message and change have been imported.
$partial_import = PhabricatorRepositoryCommit::IMPORTED_MESSAGE |
PhabricatorRepositoryCommit::IMPORTED_CHANGE;
if (!$commit->isPartiallyImported($partial_import)) {
return phutil_tag('em', array(), pht("Importing\xE2\x80\xA6"));
}
return $commit->getCommitData()->getSummary();
}
private function getCommitURI($hash) {
$repository = $this->getRepository();
if ($repository) {
return $repository->getCommitURI($hash);
}
$commit = $this->getCommit($hash);
if ($commit) {
return $commit->getURI();
}
return null;
}
private function getCommitObjectName($hash) {
$repository = $this->getRepository();
if ($repository) {
return $repository->formatCommitName(
$hash,
$is_local = true);
}
$commit = $this->getCommit($hash);
if ($commit) {
return $commit->getDisplayName();
}
return null;
}
private function getCommitIsDisabled($commit) {
if (!$commit) {
return true;
}
if ($commit->isUnreachable()) {
return true;
}
return false;
}
private function getCommitAuthorView($commit) {
if (!$commit) {
return null;
}
$viewer = $this->getViewer();
$author_phid = $commit->getAuthorDisplayPHID();
if ($author_phid) {
return $viewer->loadHandles(array($author_phid))
->newListView();
}
return $commit->newCommitAuthorView($viewer);
}
private function getCommit($hash) {
$commit_map = $this->getCommitMap();
return idx($commit_map, $hash);
}
private function getCommitMap() {
if ($this->commitMap === null) {
$commit_list = $this->newCommitList();
$this->commitMap = mpull($commit_list, null, 'getCommitIdentifier');
}
return $this->commitMap;
}
private function addBrowseAction(PHUIObjectItemView $item, $hash) {
$repository = $this->getRepository();
if (!$repository) {
return;
}
$drequest = $this->getDiffusionRequest();
$path = $drequest->getPath();
$uri = $drequest->generateURI(
array(
'action' => 'browse',
'path' => $path,
'commit' => $hash,
));
$menu_item = $item->newMenuItem()
->setName(pht('Browse Repository'))
->setURI($uri);
$menu_item->newIcon()
->setIcon('fa-folder-open-o')
->setColor('bluegrey');
}
private function addBuildAction(PHUIObjectItemView $item, $hash) {
$is_disabled = true;
$buildable = null;
$commit = $this->getCommit($hash);
if ($commit) {
$buildable = $this->getBuildable($commit);
}
if ($buildable) {
$icon = $buildable->getStatusIcon();
$color = $buildable->getStatusColor();
$name = $buildable->getStatusDisplayName();
$uri = $buildable->getURI();
} else {
$icon = 'fa-times';
$color = 'grey';
$name = pht('No Builds');
$uri = null;
}
$menu_item = $item->newMenuItem()
->setName($name)
->setURI($uri)
->setDisabled(($uri === null));
$menu_item->newIcon()
->setIcon($icon)
->setColor($color);
}
private function addAuditAction(PHUIObjectItemView $item_view, $hash) {
$commit = $this->getCommit($hash);
if ($commit) {
$status = $commit->getAuditStatusObject();
$text = $status->getName();
$icon = $status->getIcon();
$is_disabled = $status->isNoAudit();
if ($is_disabled) {
$uri = null;
$color = 'grey';
} else {
$color = $status->getColor();
$uri = $commit->getURI();
}
} else {
$text = pht('No Audit');
$color = 'grey';
$icon = 'fa-times';
$uri = null;
$is_disabled = true;
}
$menu_item = $item_view->newMenuItem()
->setName($text)
->setURI($uri)
->setBackgroundColor($color)
->setDisabled($is_disabled);
$menu_item->newIcon()
->setIcon($icon)
->setColor($color);
}
private function getBuildable(PhabricatorRepositoryCommit $commit) {
$buildable_map = $this->getBuildableMap();
return idx($buildable_map, $commit->getPHID());
}
private function getBuildableMap() {
if ($this->buildableMap === null) {
$commits = $this->getCommitMap();
$buildables = $this->loadBuildables($commits);
$this->buildableMap = $buildables;
}
return $this->buildableMap;
}
private function getRevisions(PhabricatorRepositoryCommit $commit) {
$revision_map = $this->getRevisionMap();
return idx($revision_map, $commit->getPHID(), array());
}
private function getRevisionMap() {
if ($this->revisionMap === null) {
$this->revisionMap = $this->newRevisionMap();
}
return $this->revisionMap;
}
private function newRevisionMap() {
$viewer = $this->getViewer();
$commits = $this->getCommitMap();
return DiffusionCommitRevisionQuery::loadRevisionMapForCommits(
$viewer,
$commits);
}
private function newCommitList() {
$commits = $this->getCommits();
if ($commits !== null) {
return $commits;
}
$repository = $this->getRepository();
if (!$repository) {
return array();
}
$history = $this->getHistory();
if ($history === null) {
return array();
}
$identifiers = array();
foreach ($history as $item) {
$identifiers[] = $item->getCommitIdentifier();
}
if (!$identifiers) {
return array();
}
$viewer = $this->getViewer();
$commits = id(new DiffusionCommitQuery())
->setViewer($viewer)
->withRepository($repository)
->withIdentifiers($identifiers)
->needCommitData(true)
->needIdentities(true)
->execute();
return $commits;
}
private function newAuditorList(
PhabricatorRepositoryCommit $commit,
$handles) {
$auditors = $commit->getAudits();
if (!$auditors) {
return phutil_tag('em', array(), pht('None'));
}
$auditor_phids = mpull($auditors, 'getAuditorPHID');
$auditor_list = $handles->newSublist($auditor_phids)
->newListView();
return $auditor_list;
}
}

View File

@@ -1,177 +0,0 @@
<?php
final class DiffusionCommitListView extends AphrontView {
private $commits = array();
private $noDataString;
public function setNoDataString($no_data_string) {
$this->noDataString = $no_data_string;
return $this;
}
public function setHeader($header) {
$this->header = $header;
return $this;
}
public function setCommits(array $commits) {
assert_instances_of($commits, 'PhabricatorRepositoryCommit');
$this->commits = mpull($commits, null, 'getPHID');
return $this;
}
public function getCommits() {
return $this->commits;
}
public function setHandles(array $handles) {
assert_instances_of($handles, 'PhabricatorObjectHandle');
$this->handles = $handles;
return $this;
}
private function getRequiredHandlePHIDs() {
$phids = array();
foreach ($this->history as $item) {
$data = $item->getCommitData();
if ($data) {
if ($data->getCommitDetail('authorPHID')) {
$phids[$data->getCommitDetail('authorPHID')] = true;
}
if ($data->getCommitDetail('committerPHID')) {
$phids[$data->getCommitDetail('committerPHID')] = true;
}
}
}
return array_keys($phids);
}
private function getCommitDescription($phid) {
if ($this->commits === null) {
return pht('(Unknown Commit)');
}
$commit = idx($this->commits, $phid);
if (!$commit) {
return pht('(Unknown Commit)');
}
$summary = $commit->getCommitData()->getSummary();
if (strlen($summary)) {
return $summary;
}
// No summary, so either this is still importing or just has an empty
// commit message.
if (!$commit->isImported()) {
return pht('(Importing Commit...)');
} else {
return pht('(Untitled Commit)');
}
}
public function render() {
require_celerity_resource('diffusion-css');
return $this->buildList();
}
public function buildList() {
$viewer = $this->getViewer();
$rowc = array();
$phids = array();
foreach ($this->getCommits() as $commit) {
$phids[] = $commit->getPHID();
$author_phid = $commit->getAuthorPHID();
if ($author_phid) {
$phids[] = $author_phid;
}
}
$handles = $viewer->loadHandles($phids);
$cur_date = 0;
$view = array();
foreach ($this->commits as $commit) {
$new_date = phabricator_date($commit->getEpoch(), $viewer);
if ($cur_date !== $new_date) {
$date = ucfirst(
phabricator_relative_date($commit->getEpoch(), $viewer));
$header = id(new PHUIHeaderView())
->setHeader($date);
$list = id(new PHUIObjectItemListView())
->setFlush(true)
->addClass('diffusion-history-list');
$view[] = id(new PHUIObjectBoxView())
->setHeader($header)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setObjectList($list);
}
$commit_phid = $commit->getPHID();
$commit_handle = $handles[$commit_phid];
$committed = null;
$commit_name = $commit_handle->getName();
$commit_link = $commit_handle->getURI();
$commit_desc = $this->getCommitDescription($commit_phid);
$committed = phabricator_datetime($commit->getEpoch(), $viewer);
$engine = PhabricatorMarkupEngine::newDifferentialMarkupEngine();
$engine->setConfig('viewer', $viewer);
$commit_data = $commit->getCommitData();
$message = $commit_data->getCommitMessage();
$message = $engine->markupText($message);
$message = phutil_tag_div(
'diffusion-history-message phabricator-remarkup', $message);
$author_phid = $commit->getAuthorPHID();
if ($author_phid) {
$author_name = $handles[$author_phid]->renderLink();
$author_image_uri = $handles[$author_phid]->getImageURI();
} else {
$author_name = $commit->getCommitData()->getAuthorName();
$author_image_uri =
celerity_get_resource_uri('/rsrc/image/people/user0.png');
}
$commit_tag = id(new PHUITagView())
->setName($commit_name)
->setType(PHUITagView::TYPE_SHADE)
->setColor(PHUITagView::COLOR_INDIGO)
->setBorder(PHUITagView::BORDER_NONE)
->setSlimShady(true);
$item = id(new PHUIObjectItemView())
->setHeader($commit_desc)
->setHref($commit_link)
->setDisabled($commit->isUnreachable())
->setDescription($message)
->setImageURI($author_image_uri)
->addByline(pht('Author: %s', $author_name))
->addIcon('none', $committed)
->addAttribute($commit_tag);
$list->addItem($item);
$cur_date = $new_date;
}
if (!$view) {
$list = id(new PHUIObjectItemListView())
->setFlush(true)
->setNoDataString($this->noDataString);
$view = id(new PHUIObjectBoxView())
->setHeaderText(pht('Recent Commits'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setObjectList($list);
}
return $view;
}
}

View File

@@ -1,172 +0,0 @@
<?php
final class DiffusionHistoryListView extends DiffusionHistoryView {
public function render() {
$drequest = $this->getDiffusionRequest();
$viewer = $this->getUser();
$repository = $drequest->getRepository();
require_celerity_resource('diffusion-css');
Javelin::initBehavior('phabricator-tooltips');
$buildables = $this->loadBuildables(
mpull($this->getHistory(), 'getCommit'));
$show_revisions = PhabricatorApplication::isClassInstalledForViewer(
'PhabricatorDifferentialApplication',
$viewer);
$handles = $viewer->loadHandles($this->getRequiredHandlePHIDs());
$show_builds = PhabricatorApplication::isClassInstalledForViewer(
'PhabricatorHarbormasterApplication',
$this->getUser());
$cur_date = null;
$view = array();
foreach ($this->getHistory() as $history) {
$epoch = $history->getEpoch();
$new_date = phabricator_date($history->getEpoch(), $viewer);
if ($cur_date !== $new_date) {
$date = ucfirst(
phabricator_relative_date($history->getEpoch(), $viewer));
$header = id(new PHUIHeaderView())
->setHeader($date);
$list = id(new PHUIObjectItemListView())
->setFlush(true)
->addClass('diffusion-history-list');
$view[] = id(new PHUIObjectBoxView())
->setHeader($header)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->addClass('diffusion-mobile-view')
->setObjectList($list);
}
if ($epoch) {
$committed = $viewer->formatShortDateTime($epoch);
} else {
$committed = null;
}
$data = $history->getCommitData();
$author_phid = $committer = $committer_phid = null;
if ($data) {
$author_phid = $data->getCommitDetail('authorPHID');
$committer_phid = $data->getCommitDetail('committerPHID');
$committer = $data->getCommitDetail('committer');
}
if ($author_phid && isset($handles[$author_phid])) {
$author_name = $handles[$author_phid]->renderLink();
$author_image = $handles[$author_phid]->getImageURI();
} else {
$author_name = self::renderName($history->getAuthorName());
$author_image =
celerity_get_resource_uri('/rsrc/image/people/user0.png');
}
$different_committer = false;
if ($committer_phid) {
$different_committer = ($committer_phid != $author_phid);
} else if ($committer != '') {
$different_committer = ($committer != $history->getAuthorName());
}
if ($different_committer) {
if ($committer_phid && isset($handles[$committer_phid])) {
$committer = $handles[$committer_phid]->renderLink();
} else {
$committer = self::renderName($committer);
}
$author_name = hsprintf('%s / %s', $author_name, $committer);
}
// We can show details once the message and change have been imported.
$partial_import = PhabricatorRepositoryCommit::IMPORTED_MESSAGE |
PhabricatorRepositoryCommit::IMPORTED_CHANGE;
$commit = $history->getCommit();
if ($commit && $commit->isPartiallyImported($partial_import) && $data) {
$commit_desc = $history->getSummary();
} else {
$commit_desc = phutil_tag('em', array(), pht("Importing\xE2\x80\xA6"));
}
$browse_button = $this->linkBrowse(
$history->getPath(),
array(
'commit' => $history->getCommitIdentifier(),
'branch' => $drequest->getBranch(),
'type' => $history->getFileType(),
),
true);
$diff_tag = null;
if ($show_revisions && $commit) {
$revisions = $this->getRevisionsForCommit($commit);
if ($revisions) {
$revision = head($revisions);
$diff_tag = id(new PHUITagView())
->setName($revision->getMonogram())
->setType(PHUITagView::TYPE_SHADE)
->setColor(PHUITagView::COLOR_BLUE)
->setHref($revision->getURI())
->setBorder(PHUITagView::BORDER_NONE)
->setSlimShady(true);
}
}
$build_view = null;
if ($show_builds) {
$buildable = idx($buildables, $commit->getPHID());
if ($buildable !== null) {
$build_view = $this->renderBuildable($buildable, 'button');
}
}
$message = null;
$commit_link = $repository->getCommitURI(
$history->getCommitIdentifier());
$commit_name = $repository->formatCommitName(
$history->getCommitIdentifier(), $local = true);
$committed = phabricator_datetime($commit->getEpoch(), $viewer);
$author_name = phutil_tag(
'strong',
array(
'class' => 'diffusion-history-author-name',
),
$author_name);
$authored = pht('%s on %s.', $author_name, $committed);
$commit_tag = id(new PHUITagView())
->setName($commit_name)
->setType(PHUITagView::TYPE_SHADE)
->setColor(PHUITagView::COLOR_INDIGO)
->setBorder(PHUITagView::BORDER_NONE)
->setSlimShady(true);
$item = id(new PHUIObjectItemView())
->setHeader($commit_desc)
->setHref($commit_link)
->setDisabled($commit->isUnreachable())
->setDescription($message)
->setImageURI($author_image)
->addAttribute(array($commit_tag, ' ', $diff_tag)) // For Copy Pasta
->addAttribute($authored)
->setSideColumn(array(
$build_view,
$browse_button,
));
$list->addItem($item);
$cur_date = $new_date;
}
return $view;
}
}

View File

@@ -1,208 +0,0 @@
<?php
final class DiffusionHistoryTableView extends DiffusionHistoryView {
public function render() {
$drequest = $this->getDiffusionRequest();
$viewer = $this->getUser();
$buildables = $this->loadBuildables(
mpull($this->getHistory(), 'getCommit'));
$has_any_build = false;
$show_revisions = PhabricatorApplication::isClassInstalledForViewer(
'PhabricatorDifferentialApplication',
$viewer);
$handles = $viewer->loadHandles($this->getRequiredHandlePHIDs());
$graph = null;
if ($this->getParents()) {
$parents = $this->getParents();
// If we're filtering parents, remove relationships which point to
// commits that are not part of the visible graph. Otherwise, we get
// a big tree of nonsense when viewing release branches like "stable"
// versus "master".
if ($this->getFilterParents()) {
foreach ($parents as $key => $nodes) {
foreach ($nodes as $nkey => $node) {
if (empty($parents[$node])) {
unset($parents[$key][$nkey]);
}
}
}
}
$graph = id(new PHUIDiffGraphView())
->setIsHead($this->getIsHead())
->setIsTail($this->getIsTail())
->renderGraph($parents);
}
$show_builds = PhabricatorApplication::isClassInstalledForViewer(
'PhabricatorHarbormasterApplication',
$this->getUser());
$rows = array();
$ii = 0;
foreach ($this->getHistory() as $history) {
$epoch = $history->getEpoch();
if ($epoch) {
$committed = $viewer->formatShortDateTime($epoch);
} else {
$committed = null;
}
$data = $history->getCommitData();
$author_phid = $committer = $committer_phid = null;
if ($data) {
$author_phid = $data->getCommitDetail('authorPHID');
$committer_phid = $data->getCommitDetail('committerPHID');
$committer = $data->getCommitDetail('committer');
}
if ($author_phid && isset($handles[$author_phid])) {
$author = $handles[$author_phid]->renderLink();
} else {
$author = self::renderName($history->getAuthorName());
}
$different_committer = false;
if ($committer_phid) {
$different_committer = ($committer_phid != $author_phid);
} else if ($committer != '') {
$different_committer = ($committer != $history->getAuthorName());
}
if ($different_committer) {
if ($committer_phid && isset($handles[$committer_phid])) {
$committer = $handles[$committer_phid]->renderLink();
} else {
$committer = self::renderName($committer);
}
$author = hsprintf('%s/%s', $author, $committer);
}
// We can show details once the message and change have been imported.
$partial_import = PhabricatorRepositoryCommit::IMPORTED_MESSAGE |
PhabricatorRepositoryCommit::IMPORTED_CHANGE;
$commit = $history->getCommit();
if ($commit && $commit->isPartiallyImported($partial_import) && $data) {
$summary = AphrontTableView::renderSingleDisplayLine(
$history->getSummary());
} else {
$summary = phutil_tag('em', array(), pht("Importing\xE2\x80\xA6"));
}
$build = null;
if ($show_builds) {
$buildable = idx($buildables, $commit->getPHID());
if ($buildable !== null) {
$build = $this->renderBuildable($buildable);
$has_any_build = true;
}
}
$browse = $this->linkBrowse(
$history->getPath(),
array(
'commit' => $history->getCommitIdentifier(),
'branch' => $drequest->getBranch(),
'type' => $history->getFileType(),
));
$status = $commit->getAuditStatusObject();
$icon = $status->getIcon();
$color = $status->getColor();
$name = $status->getName();
$audit_view = id(new PHUIIconView())
->setIcon($icon, $color)
->addSigil('has-tooltip')
->setMetadata(
array(
'tip' => $name,
));
$revision_link = null;
if ($commit) {
$revisions = $this->getRevisionsForCommit($commit);
if ($revisions) {
$revision = head($revisions);
$revision_link = phutil_tag(
'a',
array(
'href' => $revision->getURI(),
),
$revision->getMonogram());
}
}
$rows[] = array(
$graph ? $graph[$ii++] : null,
$browse,
self::linkCommit(
$drequest->getRepository(),
$history->getCommitIdentifier()),
$build,
$audit_view,
$revision_link,
$author,
$summary,
$committed,
);
}
$view = new AphrontTableView($rows);
$view->setHeaders(
array(
null,
null,
pht('Commit'),
null,
null,
null,
pht('Author'),
pht('Details'),
pht('Committed'),
));
$view->setColumnClasses(
array(
'threads',
'nudgeright',
'',
'icon',
'icon',
'',
'',
'wide',
'right',
));
$view->setColumnVisibility(
array(
$graph ? true : false,
true,
true,
$has_any_build,
true,
$show_revisions,
));
$view->setDeviceVisibility(
array(
$graph ? true : false,
true,
true,
true,
true,
true,
false,
true,
false,
));
return $view->render();
}
}

View File

@@ -1,117 +0,0 @@
<?php
abstract class DiffusionHistoryView extends DiffusionView {
private $history;
private $revisions = array();
private $handles = array();
private $isHead;
private $isTail;
private $parents;
private $filterParents;
private $revisionMap;
public function setHistory(array $history) {
assert_instances_of($history, 'DiffusionPathChange');
$this->history = $history;
return $this;
}
public function getHistory() {
return $this->history;
}
public function setHandles(array $handles) {
assert_instances_of($handles, 'PhabricatorObjectHandle');
$this->handles = $handles;
return $this;
}
public function getRequiredHandlePHIDs() {
$phids = array();
foreach ($this->history as $item) {
$data = $item->getCommitData();
if ($data) {
if ($data->getCommitDetail('authorPHID')) {
$phids[$data->getCommitDetail('authorPHID')] = true;
}
if ($data->getCommitDetail('committerPHID')) {
$phids[$data->getCommitDetail('committerPHID')] = true;
}
}
}
return array_keys($phids);
}
public function setParents(array $parents) {
$this->parents = $parents;
return $this;
}
public function getParents() {
return $this->parents;
}
public function setIsHead($is_head) {
$this->isHead = $is_head;
return $this;
}
public function getIsHead() {
return $this->isHead;
}
public function setIsTail($is_tail) {
$this->isTail = $is_tail;
return $this;
}
public function getIsTail() {
return $this->isTail;
}
public function setFilterParents($filter_parents) {
$this->filterParents = $filter_parents;
return $this;
}
public function getFilterParents() {
return $this->filterParents;
}
public function render() {}
final protected function getRevisionsForCommit(
PhabricatorRepositoryCommit $commit) {
if ($this->revisionMap === null) {
$this->revisionMap = $this->newRevisionMap();
}
return idx($this->revisionMap, $commit->getPHID(), array());
}
private function newRevisionMap() {
$history = $this->history;
$commits = array();
foreach ($history as $item) {
$commit = $item->getCommit();
if ($commit) {
// NOTE: The "commit" objects in the history list may be undiscovered,
// and thus not yet have PHIDs. Only load data for commits with PHIDs.
if (!$commit->getPHID()) {
continue;
}
$commits[] = $commit;
}
}
return DiffusionCommitRevisionQuery::loadRevisionMapForCommits(
$this->getViewer(),
$commits);
}
}

View File

@@ -150,7 +150,7 @@ final class DiffusionTagListView extends DiffusionView {
if ($commit->getAuthorPHID()) {
$author = $this->handles[$commit->getAuthorPHID()]->renderLink();
} else if ($commit->getCommitData()) {
$author = self::renderName($commit->getCommitData()->getAuthorName());
$author = self::renderName($commit->getCommitData()->getAuthorString());
} else {
$author = self::renderName($tag->getAuthor());
}

View File

@@ -1,140 +0,0 @@
<?php
final class DiffusionTagTableView extends DiffusionView {
private $tags;
private $commits = array();
private $handles = array();
public function setTags($tags) {
$this->tags = $tags;
return $this;
}
public function setCommits(array $commits) {
$this->commits = mpull($commits, null, 'getCommitIdentifier');
return $this;
}
public function setHandles(array $handles) {
$this->handles = $handles;
return $this;
}
public function getRequiredHandlePHIDs() {
return array_filter(mpull($this->commits, 'getAuthorPHID'));
}
public function render() {
$drequest = $this->getDiffusionRequest();
$repository = $drequest->getRepository();
$viewer = $this->getViewer();
$buildables = $this->loadBuildables($this->commits);
$has_builds = false;
$rows = array();
foreach ($this->tags as $tag) {
$commit = idx($this->commits, $tag->getCommitIdentifier());
$tag_link = phutil_tag(
'a',
array(
'href' => $drequest->generateURI(
array(
'action' => 'browse',
'commit' => $tag->getName(),
)),
),
$tag->getName());
$commit_link = phutil_tag(
'a',
array(
'href' => $drequest->generateURI(
array(
'action' => 'commit',
'commit' => $tag->getCommitIdentifier(),
)),
),
$repository->formatCommitName(
$tag->getCommitIdentifier()));
$author = null;
if ($commit && $commit->getAuthorPHID()) {
$author = $this->handles[$commit->getAuthorPHID()]->renderLink();
} else if ($commit && $commit->getCommitData()) {
$author = self::renderName($commit->getCommitData()->getAuthorName());
} else {
$author = self::renderName($tag->getAuthor());
}
$description = null;
if ($tag->getType() == 'git/tag') {
// In Git, a tag may be a "real" tag, or just a reference to a commit.
// If it's a real tag, use the message on the tag, since this may be
// unique data which isn't otherwise available.
$description = $tag->getDescription();
} else {
if ($commit) {
$description = $commit->getSummary();
} else {
$description = $tag->getDescription();
}
}
$build = null;
if ($commit) {
$buildable = idx($buildables, $commit->getPHID());
if ($buildable) {
$build = $this->renderBuildable($buildable);
$has_builds = true;
}
}
$history = $this->linkTagHistory($tag->getName());
$rows[] = array(
$history,
$tag_link,
$commit_link,
$build,
$author,
$description,
$viewer->formatShortDateTime($tag->getEpoch()),
);
}
$table = id(new AphrontTableView($rows))
->setHeaders(
array(
null,
pht('Tag'),
pht('Commit'),
null,
pht('Author'),
pht('Description'),
pht('Created'),
))
->setColumnClasses(
array(
'nudgeright',
'pri',
'',
'',
'',
'wide',
'right',
))
->setColumnVisibility(
array(
true,
true,
true,
$has_builds,
));
return $table->render();
}
}

View File

@@ -55,6 +55,12 @@ final class DrydockRepositoryOperation extends DrydockDAO
'key_repository' => array(
'columns' => array('repositoryPHID', 'operationState'),
),
'key_state' => array(
'columns' => array('operationState'),
),
'key_author' => array(
'columns' => array('authorPHID', 'operationState'),
),
),
) + parent::getConfiguration();
}

View File

@@ -43,6 +43,7 @@ final class PhabricatorChartFunctionArgumentParser
pht(
'Chart function "%s" emitted an argument specification ("%s") with '.
'no type. Each argument specification must have a valid type.',
$this->getFunctionArgumentSignature(),
$name));
}

View File

@@ -6,7 +6,7 @@ final class PhabricatorFactDaemon extends PhabricatorDaemon {
protected function run() {
$this->setEngines(PhabricatorFactEngine::loadAllEngines());
while (!$this->shouldExit()) {
do {
PhabricatorCaches::destroyRequestCache();
$iterators = $this->getAllApplicationIterators();
@@ -14,9 +14,14 @@ final class PhabricatorFactDaemon extends PhabricatorDaemon {
$this->processIteratorWithCursor($iterator_name, $iterator);
}
$this->log(pht('Zzz...'));
$this->sleep(15);
}
$sleep_duration = 60;
if ($this->shouldHibernate($sleep_duration)) {
break;
}
$this->sleep($sleep_duration);
} while (!$this->shouldExit());
}
public static function getAllApplicationIterators() {

View File

@@ -85,15 +85,8 @@ final class PhabricatorJupyterDocumentEngine
if ($utype === $vtype) {
switch ($utype) {
case 'markdown':
$usource = idx($ucell, 'source');
if (is_array($usource)) {
$usource = implode('', $usource);
}
$vsource = idx($vcell, 'source');
if (is_array($vsource)) {
$vsource = implode('', $vsource);
}
$usource = $this->readString($ucell, 'source');
$vsource = $this->readString($vcell, 'source');
$diff = id(new PhutilProseDifferenceEngine())
->getDiff($usource, $vsource);
@@ -117,8 +110,6 @@ final class PhabricatorJupyterDocumentEngine
$vsource = idx($vcell, 'raw');
$udisplay = idx($ucell, 'display');
$vdisplay = idx($vcell, 'display');
$ulabel = idx($ucell, 'label');
$vlabel = idx($vcell, 'label');
$intraline_segments = ArcanistDiffUtils::generateIntralineDiff(
$usource,
@@ -142,15 +133,15 @@ final class PhabricatorJupyterDocumentEngine
$vdisplay,
$v_segments);
$u_content = $this->newCodeLineCell($ucell, $usource);
$v_content = $this->newCodeLineCell($vcell, $vsource);
list($u_label, $u_content) = $this->newCodeLineCell($ucell, $usource);
list($v_label, $v_content) = $this->newCodeLineCell($vcell, $vsource);
$classes = array(
'jupyter-cell-flush',
);
$u_content = $this->newJupyterCell($ulabel, $u_content, $classes);
$v_content = $this->newJupyterCell($vlabel, $v_content, $classes);
$u_content = $this->newJupyterCell($u_label, $u_content, $classes);
$v_content = $this->newJupyterCell($v_label, $v_content, $classes);
$u_content = $this->newCellContainer($u_content);
$v_content = $this->newCellContainer($v_content);
@@ -259,10 +250,7 @@ final class PhabricatorJupyterDocumentEngine
$hash_input = $cell['raw'];
break;
case 'markdown':
$hash_input = $cell['source'];
if (is_array($hash_input)) {
$hash_input = implode('', $cell['source']);
}
$hash_input = $this->readString($cell, 'source');
break;
default:
$hash_input = serialize($cell);
@@ -334,7 +322,6 @@ final class PhabricatorJupyterDocumentEngine
'be rendered as a Jupyter notebook.'));
}
$nbformat = idx($data, 'nbformat');
if (!strlen($nbformat)) {
throw new Exception(
@@ -376,10 +363,7 @@ final class PhabricatorJupyterDocumentEngine
foreach ($cells as $cell) {
$cell_type = idx($cell, 'cell_type');
if ($cell_type === 'markdown') {
$source = $cell['source'];
if (is_array($source)) {
$source = implode('', $source);
}
$source = $this->readString($cell, 'source');
// Attempt to split contiguous blocks of markdown into smaller
// pieces.
@@ -404,11 +388,7 @@ final class PhabricatorJupyterDocumentEngine
$label = $this->newCellLabel($cell);
$lines = idx($cell, 'source');
if (!is_array($lines)) {
$lines = array();
}
$lines = $this->readStringList($cell, 'source');
$content = $this->highlightLines($lines);
$count = count($lines);
@@ -526,10 +506,7 @@ final class PhabricatorJupyterDocumentEngine
}
private function newMarkdownCell(array $cell) {
$content = idx($cell, 'source');
if (!is_array($content)) {
$content = array();
}
$content = $this->readStringList($cell, 'source');
// TODO: This should ideally highlight as Markdown, but the "md"
// highlighter in Pygments is painfully slow and not terribly useful.
@@ -549,11 +526,7 @@ final class PhabricatorJupyterDocumentEngine
private function newCodeCell(array $cell) {
$label = $this->newCellLabel($cell);
$content = idx($cell, 'source');
if (!is_array($content)) {
$content = array();
}
$content = $this->readStringList($cell, 'source');
$content = $this->highlightLines($content);
$outputs = array();
@@ -660,11 +633,7 @@ final class PhabricatorJupyterDocumentEngine
continue;
}
$raw_data = $data[$image_format];
if (!is_array($raw_data)) {
$raw_data = array($raw_data);
}
$raw_data = implode('', $raw_data);
$raw_data = $this->readString($data, $image_format);
$content = phutil_tag(
'img',
@@ -695,11 +664,7 @@ final class PhabricatorJupyterDocumentEngine
break;
case 'stream':
default:
$content = idx($output, 'text');
if (!is_array($content)) {
$content = array();
}
$content = implode('', $content);
$content = $this->readString($output, 'text');
break;
}
@@ -761,4 +726,23 @@ final class PhabricatorJupyterDocumentEngine
return true;
}
private function readString(array $src, $key) {
$list = $this->readStringList($src, $key);
return implode('', $list);
}
private function readStringList(array $src, $key) {
$list = idx($src, $key);
if (is_array($list)) {
$list = $list;
} else if (is_string($list)) {
$list = array($list);
} else {
$list = array();
}
return $list;
}
}

View File

@@ -12,4 +12,16 @@ final class PhabricatorFileHasObjectEdgeType extends PhabricatorEdgeType {
return true;
}
public function getConduitKey() {
return 'file.attached-objects';
}
public function getConduitName() {
return pht('File Has Object');
}
public function getConduitDescription() {
return pht('The source file is attached to the destination object.');
}
}

View File

@@ -0,0 +1,20 @@
<?php
final class HarbormasterBuildStepEditAPIMethod
extends PhabricatorEditEngineAPIMethod {
public function getAPIMethodName() {
return 'harbormaster.step.edit';
}
public function newEditEngine() {
return new HarbormasterBuildStepEditEngine();
}
public function getMethodSummary() {
return pht(
'Apply transactions to create a new build step or edit an existing '.
'one.');
}
}

View File

@@ -0,0 +1,18 @@
<?php
final class HarbormasterBuildStepSearchAPIMethod
extends PhabricatorSearchEngineAPIMethod {
public function getAPIMethodName() {
return 'harbormaster.step.search';
}
public function newSearchEngine() {
return new HarbormasterBuildStepSearchEngine();
}
public function getMethodSummary() {
return pht('Retrieve information about Harbormaster build steps.');
}
}

View File

@@ -0,0 +1,107 @@
<?php
final class HarbormasterBuildStepEditEngine
extends PhabricatorEditEngine {
const ENGINECONST = 'harbormaster.buildstep';
private $buildPlan;
public function setBuildPlan(HarbormasterBuildPlan $build_plan) {
$this->buildPlan = $build_plan;
return $this;
}
public function getBuildPlan() {
if ($this->buildPlan === null) {
throw new PhutilInvalidStateException('setBuildPlan');
}
return $this->buildPlan;
}
public function isEngineConfigurable() {
return false;
}
public function getEngineName() {
return pht('Harbormaster Build Steps');
}
public function getSummaryHeader() {
return pht('Edit Harbormaster Build Step Configurations');
}
public function getSummaryText() {
return pht('This engine is used to edit Harbormaster build steps.');
}
public function getEngineApplicationClass() {
return 'PhabricatorHarbormasterApplication';
}
protected function newEditableObject() {
$viewer = $this->getViewer();
$plan = HarbormasterBuildPlan::initializeNewBuildPlan($viewer);
$this->setBuildPlan($plan);
$plan = $this->getBuildPlan();
$step = HarbormasterBuildStep::initializeNewStep($viewer);
$step->setBuildPlanPHID($plan->getPHID());
$step->attachBuildPlan($plan);
return $step;
}
protected function newObjectQuery() {
return new HarbormasterBuildStepQuery();
}
protected function getObjectCreateTitleText($object) {
return pht('Create Build Step');
}
protected function getObjectCreateButtonText($object) {
return pht('Create Build Step');
}
protected function getObjectEditTitleText($object) {
return pht('Edit Build Step: %s', $object->getName());
}
protected function getObjectEditShortText($object) {
return pht('Edit Build Step');
}
protected function getObjectCreateShortText() {
return pht('Create Build Step');
}
protected function getObjectName() {
return pht('Build Step');
}
protected function getEditorURI() {
return '/harbormaster/step/edit/';
}
protected function getObjectCreateCancelURI($object) {
return '/harbormaster/step/';
}
protected function getObjectViewURI($object) {
$id = $object->getID();
return "/harbormaster/step/{$id}/";
}
protected function buildCustomEditFields($object) {
$fields = array();
return $fields;
}
}

View File

@@ -54,6 +54,7 @@ final class HarbormasterManagementPublishWorkflow
pht(
'Object "%s" is not a HarbormasterBuildable (it is a "%s"). '.
'Name one or more buildables to publish, like "B123".',
$name,
get_class($result)));
}
}

View File

@@ -0,0 +1,58 @@
<?php
final class HarbormasterBuildStepSearchEngine
extends PhabricatorApplicationSearchEngine {
public function getResultTypeDescription() {
return pht('Harbormaster Build Steps');
}
public function getApplicationClassName() {
return 'PhabricatorHarbormasterApplication';
}
public function newQuery() {
return new HarbormasterBuildStepQuery();
}
protected function buildCustomSearchFields() {
return array();
}
protected function buildQueryFromParameters(array $map) {
$query = $this->newQuery();
return $query;
}
protected function getURI($path) {
return '/harbormaster/step/'.$path;
}
protected function getBuiltinQueryNames() {
return array(
'all' => pht('All Steps'),
);
}
public function buildSavedQueryFromBuiltin($query_key) {
$query = $this->newSavedQuery();
$query->setQueryKey($query_key);
switch ($query_key) {
case 'all':
return $query;
}
return parent::buildSavedQueryFromBuiltin($query_key);
}
protected function renderResultList(
array $plans,
PhabricatorSavedQuery $query,
array $handles) {
assert_instances_of($plans, 'HarbormasterBuildStep');
return null;
}
}

View File

@@ -14,7 +14,7 @@ final class HarbormasterWaitForPreviousBuildStepImplementation
}
public function getBuildStepGroupKey() {
return HarbormasterPrototypeBuildStepGroup::GROUPKEY;
return HarbormasterControlBuildStepGroup::GROUPKEY;
}
public function execute(

View File

@@ -177,6 +177,8 @@ final class HarbormasterBuildLog
pht(
'Attempt to load log bytes (%d - %d) failed: failed to '.
'load a single contiguous range. Actual ranges: %s.',
$offset,
$end,
implode('; ', $display_ranges)));
}

View File

@@ -4,7 +4,8 @@ final class HarbormasterBuildStep extends HarbormasterDAO
implements
PhabricatorApplicationTransactionInterface,
PhabricatorPolicyInterface,
PhabricatorCustomFieldInterface {
PhabricatorCustomFieldInterface,
PhabricatorConduitResultInterface {
protected $name;
protected $description;
@@ -169,5 +170,45 @@ final class HarbormasterBuildStep extends HarbormasterDAO
return $this;
}
/* -( PhabricatorConduitResultInterface )---------------------------------- */
public function getFieldSpecificationsForConduit() {
return array(
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('name')
->setType('string')
->setDescription(pht('The name of the build step.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('description')
->setType('remarkup')
->setDescription(pht('The build step description.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('buildPlanPHID')
->setType('phid')
->setDescription(
pht(
'The PHID of the build plan this build step belongs to.')),
);
}
public function getFieldValuesForConduit() {
// T6203: This can be removed once the field becomes non-nullable.
$name = $this->getName();
$name = phutil_string_cast($name);
return array(
'name' => $name,
'description' => array(
'raw' => $this->getDescription(),
),
'buildPlanPHID' => $this->getBuildPlanPHID(),
);
}
public function getConduitSearchAttachments() {
return array();
}
}

View File

@@ -0,0 +1,43 @@
<?php
final class HeraldCommentContentField extends HeraldField {
const FIELDCONST = 'comment.content';
public function getHeraldFieldName() {
return pht('Comment content');
}
public function getFieldGroupKey() {
return HeraldTransactionsFieldGroup::FIELDGROUPKEY;
}
public function getHeraldFieldValue($object) {
$adapter = $this->getAdapter();
$xactions = $adapter->getAppliedTransactions();
$result = array();
foreach ($xactions as $xaction) {
if (!$xaction->hasComment()) {
continue;
}
$comment = $xaction->getComment();
$content = $comment->getContent();
$result[] = $content;
}
return $result;
}
public function supportsObject($object) {
return true;
}
protected function getHeraldFieldStandardType() {
return self::STANDARD_TEXT_LIST;
}
}

View File

@@ -28,6 +28,17 @@ final class HeraldWebhookCallManagementWorkflow
'name' => 'secure',
'help' => pht('Set the "secure" flag on the request.'),
),
array(
'name' => 'count',
'param' => 'N',
'help' => pht('Make a total of __N__ copies of the call.'),
),
array(
'name' => 'background',
'help' => pht(
'Instead of making calls in the foreground, add the tasks '.
'to the daemon queue.'),
),
));
}
@@ -41,6 +52,17 @@ final class HeraldWebhookCallManagementWorkflow
'Specify a webhook to call with "--id".'));
}
$count = $args->getArg('count');
if ($count === null) {
$count = 1;
}
if ($count <= 0) {
throw new PhutilArgumentUsageException(
pht(
'Specified "--count" must be larger than 0.'));
}
$hook = id(new HeraldWebhookQuery())
->setViewer($viewer)
->withIDs(array($id))
@@ -69,6 +91,8 @@ final class HeraldWebhookCallManagementWorkflow
$object = head($objects);
}
$is_background = $args->getArg('background');
$xaction_query =
PhabricatorApplicationTransactionQuery::newQueryForObject($object);
@@ -80,25 +104,49 @@ final class HeraldWebhookCallManagementWorkflow
$application_phid = id(new PhabricatorHeraldApplication())->getPHID();
$request = HeraldWebhookRequest::initializeNewWebhookRequest($hook)
->setObjectPHID($object->getPHID())
->setIsTestAction(true)
->setIsSilentAction((bool)$args->getArg('silent'))
->setIsSecureAction((bool)$args->getArg('secure'))
->setTriggerPHIDs(array($application_phid))
->setTransactionPHIDs(mpull($xactions, 'getPHID'))
->save();
if ($is_background) {
echo tsprintf(
"%s\n",
pht(
'Queueing webhook calls...'));
$progress_bar = id(new PhutilConsoleProgressBar())
->setTotal($count);
} else {
echo tsprintf(
"%s\n",
pht(
'Calling webhook...'));
PhabricatorWorker::setRunAllTasksInProcess(true);
}
PhabricatorWorker::setRunAllTasksInProcess(true);
$request->queueCall();
for ($ii = 0; $ii < $count; $ii++) {
$request = HeraldWebhookRequest::initializeNewWebhookRequest($hook)
->setObjectPHID($object->getPHID())
->setIsTestAction(true)
->setIsSilentAction((bool)$args->getArg('silent'))
->setIsSecureAction((bool)$args->getArg('secure'))
->setTriggerPHIDs(array($application_phid))
->setTransactionPHIDs(mpull($xactions, 'getPHID'))
->save();
$request->reload();
$request->queueCall();
echo tsprintf(
"%s\n",
pht(
'Success, got HTTP %s from webhook.',
$request->getErrorCode()));
if ($is_background) {
$progress_bar->update(1);
} else {
$request->reload();
echo tsprintf(
"%s\n",
pht(
'Success, got HTTP %s from webhook.',
$request->getErrorCode()));
}
}
if ($is_background) {
$progress_bar->done();
}
return 0;
}

View File

@@ -199,8 +199,7 @@ final class ManiphestTaskPriority extends ManiphestConstants {
throw new Exception(
pht(
'Configuration is not valid. Maniphest priority configurations '.
'must be dictionaries.',
$config));
'must be dictionaries.'));
}
$all_keywords = array();

View File

@@ -65,6 +65,7 @@ abstract class PhabricatorMailAdapter
pht(
'Adapter ("%s") is configured for medium "%s", but this is not '.
'a supported delivery medium. Supported media are: %s.',
get_class($this),
$medium,
implode(', ', $native_map)));
}

View File

@@ -17,6 +17,7 @@ final class PhabricatorMailAmazonSESAdapter
array(
'access-key' => 'string',
'secret-key' => 'string',
'region' => 'string',
'endpoint' => 'string',
));
}
@@ -25,6 +26,7 @@ final class PhabricatorMailAmazonSESAdapter
return array(
'access-key' => null,
'secret-key' => null,
'region' => null,
'endpoint' => null,
);
}
@@ -45,23 +47,33 @@ final class PhabricatorMailAmazonSESAdapter
$mailer->Send();
}
/**
* @phutil-external-symbol class SimpleEmailService
*/
public function executeSend($body) {
$key = $this->getOption('access-key');
$secret = $this->getOption('secret-key');
$secret = new PhutilOpaqueEnvelope($secret);
$region = $this->getOption('region');
$endpoint = $this->getOption('endpoint');
$root = phutil_get_library_root('phabricator');
$root = dirname($root);
require_once $root.'/externals/amazon-ses/ses.php';
$data = array(
'Action' => 'SendRawEmail',
'RawMessage.Data' => base64_encode($body),
);
$service = new SimpleEmailService($key, $secret, $endpoint);
$service->enableUseExceptions(true);
return $service->sendRawEmail($body);
$data = phutil_build_http_querystring($data);
$future = id(new PhabricatorAWSSESFuture())
->setAccessKey($key)
->setSecretKey($secret)
->setRegion($region)
->setEndpoint($endpoint)
->setHTTPMethod('POST')
->setData($data);
$future->resolve();
return true;
}
}

View File

@@ -12,6 +12,7 @@ final class PhabricatorMailAdapterTestCase
array(
'access-key' => 'test',
'secret-key' => 'test',
'region' => 'test',
'endpoint' => 'test',
),
),

View File

@@ -0,0 +1,21 @@
<?php
final class PhabricatorAWSSESFuture extends PhutilAWSFuture {
private $parameters;
public function getServiceName() {
return 'ses';
}
protected function didReceiveResult($result) {
list($status, $body, $headers) = $result;
if (!$status->isError()) {
return $body;
}
return parent::didReceiveResult($result);
}
}

View File

@@ -64,24 +64,6 @@ final class PhabricatorMetaMTAMailQuery
$this->actorPHIDs);
}
if ($this->recipientPHIDs !== null) {
$where[] = qsprintf(
$conn,
'recipient.dst IN (%Ls)',
$this->recipientPHIDs);
}
if ($this->actorPHIDs === null && $this->recipientPHIDs === null) {
$viewer = $this->getViewer();
if (!$viewer->isOmnipotent()) {
$where[] = qsprintf(
$conn,
'edge.dst = %s OR actorPHID = %s',
$viewer->getPHID(),
$viewer->getPHID());
}
}
if ($this->createdMin !== null) {
$where[] = qsprintf(
$conn,
@@ -102,26 +84,29 @@ final class PhabricatorMetaMTAMailQuery
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
$joins = parent::buildJoinClauseParts($conn);
if ($this->actorPHIDs === null && $this->recipientPHIDs === null) {
if ($this->shouldJoinRecipients()) {
$joins[] = qsprintf(
$conn,
'LEFT JOIN %T edge ON mail.phid = edge.src AND edge.type = %d',
'JOIN %T recipient
ON mail.phid = recipient.src
AND recipient.type = %d
AND recipient.dst IN (%Ls)',
PhabricatorEdgeConfig::TABLE_NAME_EDGE,
PhabricatorMetaMTAMailHasRecipientEdgeType::EDGECONST);
}
if ($this->recipientPHIDs !== null) {
$joins[] = qsprintf(
$conn,
'LEFT JOIN %T recipient '.
'ON mail.phid = recipient.src AND recipient.type = %d',
PhabricatorEdgeConfig::TABLE_NAME_EDGE,
PhabricatorMetaMTAMailHasRecipientEdgeType::EDGECONST);
PhabricatorMetaMTAMailHasRecipientEdgeType::EDGECONST,
$this->recipientPHIDs);
}
return $joins;
}
private function shouldJoinRecipients() {
if ($this->recipientPHIDs === null) {
return false;
}
return true;
}
protected function getPrimaryTableAlias() {
return 'mail';
}
@@ -134,4 +119,14 @@ final class PhabricatorMetaMTAMailQuery
return 'PhabricatorMetaMTAApplication';
}
protected function shouldGroupQueryResultRows() {
if ($this->shouldJoinRecipients()) {
if (count($this->recipientPHIDs) > 1) {
return true;
}
}
return parent::shouldGroupQueryResultRows();
}
}

View File

@@ -98,7 +98,9 @@ final class PhabricatorNotificationServersConfigType
'Notification server configuration describes an invalid host '.
'("%s", at index "%s"). This is an "admin" service but it has a '.
'"path" property. This property is only valid for "client" '.
'services.'));
'services.',
$host,
$index));
}
// We can't guarantee that you didn't just give the same host two

View File

@@ -85,7 +85,8 @@ final class NuanceGitHubRawEventTestCase
throw new Exception(
pht(
'Expected test file "%s" to contain exactly two sections, '.
'but it has more than two sections.'));
'but it has more than two sections.',
$file));
}
list($input, $expect) = $parts;

View File

@@ -82,12 +82,15 @@ final class PhabricatorOwnersDetailController
))
->needCommitData(true)
->needAuditRequests(true)
->needIdentities(true)
->setLimit(10)
->execute();
$view = id(new PhabricatorAuditListView())
->setUser($viewer)
->setNoDataString(pht('This package has no open problem commits.'))
->setCommits($attention_commits);
$view = id(new DiffusionCommitGraphView())
->setViewer($viewer)
->setCommits($attention_commits)
->newObjectItemListView();
$view->setNoDataString(pht('This package has no open problem commits.'));
$commit_views[] = array(
'view' => $view,
@@ -105,13 +108,16 @@ final class PhabricatorOwnersDetailController
->withPackagePHIDs(array($package->getPHID()))
->needCommitData(true)
->needAuditRequests(true)
->needIdentities(true)
->setLimit(25)
->execute();
$view = id(new PhabricatorAuditListView())
->setUser($viewer)
$view = id(new DiffusionCommitGraphView())
->setViewer($viewer)
->setCommits($all_commits)
->setNoDataString(pht('No commits in this package.'));
->newObjectItemListView();
$view->setNoDataString(pht('No commits in this package.'));
$commit_views[] = array(
'view' => $view,

View File

@@ -58,6 +58,7 @@ final class PhabricatorPeopleApplication extends PhabricatorApplication {
=> 'PhabricatorPeopleDisableController',
'(?P<via>disable)/(?P<id>[1-9]\d*)/'
=> 'PhabricatorPeopleDisableController',
'spam/(?P<id>[1-9]\d*)/' => 'PhabricatorPeopleSpamController',
'empower/(?P<id>[1-9]\d*)/' => 'PhabricatorPeopleEmpowerController',
'delete/(?P<id>[1-9]\d*)/' => 'PhabricatorPeopleDeleteController',
'rename/(?P<id>[1-9]\d*)/' => 'PhabricatorPeopleRenameController',
@@ -99,6 +100,9 @@ final class PhabricatorPeopleApplication extends PhabricatorApplication {
PeopleDisableUsersCapability::CAPABILITY => array(
'default' => PhabricatorPolicies::POLICY_ADMIN,
),
PeopleDisableSpamUsersCapability::CAPABILITY => array(
'default' => PhabricatorPolicies::POLICY_ADMIN,
),
PeopleBrowseUserDirectoryCapability::CAPABILITY => array(),
);
}

View File

@@ -0,0 +1,16 @@
<?php
final class PeopleDisableSpamUsersCapability
extends PhabricatorPolicyCapability {
const CAPABILITY = 'people.disable_spam.users';
public function getCapabilityName() {
return pht('Can Disable Spammers');
}
public function describeCapabilityRejection() {
return pht('You do not have permission to handle spam users.');
}
}

View File

@@ -58,13 +58,13 @@ final class PhabricatorPeopleProfileCommitsController
->setViewer($viewer)
->withAuthorPHIDs(array($user->getPHID()))
->needCommitData(true)
->needIdentities(true)
->setLimit(100)
->execute();
$list = id(new DiffusionCommitListView())
$list = id(new DiffusionCommitGraphView())
->setViewer($viewer)
->setCommits($commits)
->setNoDataString(pht('No recent commits.'));
->setCommits($commits);
return $list;
}

View File

@@ -93,6 +93,10 @@ final class PhabricatorPeopleProfileManageController
PeopleDisableUsersCapability::CAPABILITY);
$can_disable = ($has_disable && !$is_self);
$has_disable_spam_capability = $this->hasApplicationCapability(
PeopleDisableSpamUsersCapability::CAPABILITY);
$can_disable_spam = ($has_disable_spam_capability && !$is_self);
$id = $user->getID();
$welcome_engine = id(new PhabricatorPeopleWelcomeMailEngine())
@@ -193,6 +197,14 @@ final class PhabricatorPeopleProfileManageController
->setWorkflow(true)
->setHref($this->getApplicationURI('disable/'.$id.'/')));
$curtain->addAction(
id(new PhabricatorActionView())
->setIcon('fa-shield')
->setName(pht('Disable as Spam'))
->setDisabled(!$can_disable_spam)
->setWorkflow(true)
->setHref($this->getApplicationURI('spam/'.$id.'/')));
$curtain->addAction(
id(new PhabricatorActionView())
->setIcon('fa-times')

View File

@@ -0,0 +1,90 @@
<?php
final class PhabricatorPeopleSpamController
extends PhabricatorPeopleController {
public function shouldRequireAdmin() {
return false;
}
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$id = $request->getURIData('id');
$user = id(new PhabricatorPeopleQuery())
->setViewer($viewer)
->withIDs(array($id))
->executeOne();
if (!$user) {
return new Aphront404Response();
}
$this->requireApplicationCapability(
PeopleDisableUsersCapability::CAPABILITY);
$actor = $viewer;
$done_uri = $this->getApplicationURI("manage/{$id}/");
if ($viewer->getPHID() == $user->getPHID()) {
return $this->newDialog()
->setTitle(pht('Something Stays Your Hand'))
->appendParagraph(
pht(
'Try as you might, you find you can not disable your own account.'))
->addCancelButton($done_uri, pht('Curses!'));
}
if ($request->isFormPost()) {
// Disable the account.
if (!$user->getIsDisabled()) {
$xactions = array();
$xactions[] = id(new PhabricatorUserTransaction())
->setTransactionType(PhabricatorUserDisableTransaction::TRANSACTIONTYPE)
->setNewValue(true);
id(new PhabricatorUserTransactionEditor())
->setActor($actor)
->setActingAsPHID($viewer->getPHID())
->setContentSourceFromRequest($request)
->setContinueOnMissingFields(true)
->setContinueOnNoEffect(true)
->applyTransactions($user, $xactions);
}
// Set profile info to spam and blank to everything else.
{
$xactions = array();
$xactions[] = id(new PhabricatorUserTransaction())
->setTransactionType(PhabricatorUserDisableSpamTransaction::TRANSACTIONTYPE);
id(new PhabricatorUserTransactionEditor())
->setActor($actor)
->setActingAsPHID($viewer->getPHID())
->setContentSourceFromRequest($request)
->setContinueOnMissingFields(true)
->setContinueOnNoEffect(true)
->applyTransactions($user, $xactions);
}
return id(new AphrontRedirectResponse())->setURI($done_uri);
}
$title = pht('Disable as Spam?');
$short_title = pht('Disable as Spam');
$body = pht(
'Is %s\'s profile spam?<br />All the profile info will be erased and '.
'they will no longer be able to access Phabricator.',
phutil_tag('strong', array(), $user->getUsername()));
$submit = pht('Disable as Spam');
return $this->newDialog()
->setTitle($title)
->setShortTitle($short_title)
->appendParagraph($body)
->addCancelButton($done_uri)
->addSubmitButton($submit);
}
}

View File

@@ -0,0 +1,68 @@
<?php
final class PhabricatorUserDisableSpamTransaction
extends PhabricatorUserTransactionType {
const TRANSACTIONTYPE = 'user.disable_spam';
public function generateOldValue($object) {
$user = $object;
return (string)$user->getRealName();
}
public function generateNewValue($object, $value) {
// Deliberately not using 'spam'.
// This way we can use this button even for accounts that
// have been already manually renamed to 'spam'.
// Otherwise when the name clash with the existing name
// none of the changes happens.
return 'disabled_spam';
}
public function applyInternalEffects($object, $value) {
$user = $object;
$user->setRealName('spam');
$profile = $user->loadUserProfile();
$profile->setBlurb('');
$profile->setTitle('');
$profile->setIcon('');
$file = PhabricatorFile::loadBuiltin($user, 'profile.png');
$user->setProfileImagePHID($file->getPHID());
}
public function getTitle() {
return pht(
'%s set this user as spam.',
$this->renderAuthor());
}
public function shouldHideForFeed() {
// Don't publish feed stories about handling spam.
return true;
}
public function validateTransactions($object, array $xactions) {
$errors = array();
foreach ($xactions as $xaction) {
// You must have the "Can Disable Spam Users" permission to disable a user as spam.
$this->requireApplicationCapability(
PeopleDisableSpamUsersCapability::CAPABILITY);
if ($this->getActingAsPHID() === $object->getPHID()) {
$errors[] = $this->newInvalidError(
pht('You can not disable your own account as spam.'));
}
}
return $errors;
}
public function getRequiredCapabilities(
$object,
PhabricatorApplicationTransaction $xaction) {
return null;
}
}

View File

@@ -104,6 +104,10 @@ final class PhabricatorHandleList
->setHandleList($this);
}
public function newListView() {
return id(new FuelHandleListView())
->addHandleList($this);
}
/**
* Return a @{class:PHUIHandleView} which can render a specific handle.

View File

@@ -61,6 +61,8 @@ final class PholioMockViewController extends PholioController {
new PholioTransactionQuery());
$timeline->setMock($mock);
$timeline->setQuoteRef($mock->getMonogram());
$curtain = $this->buildCurtainView($mock);
$details = $this->buildDescriptionView($mock);
@@ -80,6 +82,7 @@ final class PholioMockViewController extends PholioController {
->appendChild($mock_view);
$add_comment = $this->buildAddCommentView($mock, $comment_form_id);
$add_comment->setTransactionTimeline($timeline);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb($mock->getMonogram(), $mock->getURI());

View File

@@ -71,7 +71,6 @@ final class PhragmentRevertController extends PhragmentController {
->appendParagraph(pht(
'Reverting this fragment to version %d will create a new version of '.
'the fragment. It will not delete any version history.',
$version->getSequence(),
$version->getSequence()));
return id(new AphrontDialogResponse())->setDialog($dialog);
}

View File

@@ -401,7 +401,7 @@ final class PhrictionDocumentController
$view->addProperty(
pht('Last Edited'),
phabricator_datetime($content->getDateCreated(), $viewer));
phabricator_dual_datetime($content->getDateCreated(), $viewer));
return $view;
}

View File

@@ -266,8 +266,7 @@ final class PhabricatorPolicy
return pht(
'Members of a particular project can take this action. (You '.
'can not see this object, so the name of this project is '.
'restricted.)',
$handle->getFullName());
'restricted.)');
} else if ($type == PhabricatorPeopleUserPHIDType::TYPECONST) {
return pht(
'%s can take this action.',

View File

@@ -224,6 +224,7 @@ final class PhabricatorProjectIconSet
'Icon key "%s" is not a valid icon key. Icon keys must be 1-32 '.
'characters long and contain only lowercase letters. For example, '.
'"%s" and "%s" are reasonable keys.',
$value['key'],
'tag',
'group'));
}

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