@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.)
hq.recaptime.dev/wiki/Phorge
phorge
phabricator
1<?php
2
3final class DiffusionInternalGitRawDiffQueryConduitAPIMethod
4 extends DiffusionQueryConduitAPIMethod {
5
6 public function isInternalAPI() {
7 return true;
8 }
9
10 public function getAPIMethodName() {
11 return 'diffusion.internal.gitrawdiffquery';
12 }
13
14 public function getMethodDescription() {
15 return pht('Internal method for getting raw diff information.');
16 }
17
18 protected function defineReturnType() {
19 return 'string';
20 }
21
22 protected function defineCustomParamTypes() {
23 return array(
24 'commit' => 'required string',
25 );
26 }
27
28 protected function getResult(ConduitAPIRequest $request) {
29 $drequest = $this->getDiffusionRequest();
30 $repository = $drequest->getRepository();
31 $commit = $request->getValue('commit');
32
33 if (!$repository->isGit()) {
34 throw new Exception(
35 pht(
36 'This API method can only be called on Git repositories.'));
37 }
38
39 // Check if the commit has parents. We're testing to see whether it is the
40 // first commit in history (in which case we must use "git log") or some
41 // other commit (in which case we can use "git diff"). We'd rather use
42 // "git diff" because it has the right behavior for merge commits, but
43 // it requires the commit to have a parent that we can diff against. The
44 // first commit doesn't, so "commit^" is not a valid ref.
45 list($parents) = $repository->execxLocalCommand(
46 'log -n1 %s %s --',
47 '--format=%P',
48 gitsprintf('%s', $commit));
49 $use_log = !strlen(trim($parents));
50
51 // First, get a fast raw diff without "--find-copies-harder". This flag
52 // produces better results for moves and copies, but is explosively slow
53 // for large changes to large repositories. See T10423.
54 $raw = $this->getRawDiff($repository, $commit, $use_log, false);
55
56 // If we got a normal-sized diff (no more than 100 modified files), we'll
57 // try using "--find-copies-harder" to improve the output. This improved
58 // output is mostly useful for small changes anyway.
59 $try_harder = (substr_count($raw, "\n") <= 100);
60 if ($try_harder) {
61 try {
62 $raw = $this->getRawDiff($repository, $commit, $use_log, true);
63 } catch (Exception $ex) {
64 // Just ignore any exception we hit, we'll use the fast output
65 // instead.
66 }
67 }
68
69 return $raw;
70 }
71
72 private function getRawDiff(
73 PhabricatorRepository $repository,
74 $commit,
75 $use_log,
76 $try_harder) {
77
78 $flags = array(
79 '-n1',
80 '-M',
81 '-C',
82 '-B',
83 '--raw',
84 '-t',
85 '--abbrev=40',
86 );
87
88 if ($try_harder) {
89 $flags[] = '--find-copies-harder';
90 }
91
92 if ($use_log) {
93 // This is the first commit so we need to use "log". We know it's not a
94 // merge commit because it couldn't be merging anything, so this is safe.
95
96 // NOTE: "--pretty=format: " is to disable diff output, we only want the
97 // part we get from "--raw".
98 $future = $repository->getLocalCommandFuture(
99 'log %Ls --pretty=format: %s --',
100 $flags,
101 gitsprintf('%s', $commit));
102 } else {
103 // Otherwise, we can use "diff", which will give us output for merges.
104 // We diff against the first parent, as this is generally the expectation
105 // and results in sensible behavior.
106 $future = $repository->getLocalCommandFuture(
107 'diff %Ls %s %s --',
108 $flags,
109 gitsprintf('%s^1', $commit),
110 gitsprintf('%s', $commit));
111 }
112
113 // Don't spend more than 30 seconds generating the slower output.
114 if ($try_harder) {
115 $future->setTimeout(30);
116 }
117
118 list($raw) = $future->resolvex();
119
120 return $raw;
121 }
122
123}