2011-01-09 15:22:25 -08:00
|
|
|
<?php
|
|
|
|
|
2011-02-19 11:36:08 -08:00
|
|
|
/**
|
|
|
|
* Interfaces with Git working copies.
|
|
|
|
*
|
|
|
|
* @group workingcopy
|
|
|
|
*/
|
2012-01-31 12:07:05 -08:00
|
|
|
final class ArcanistGitAPI extends ArcanistRepositoryAPI {
|
2011-01-09 15:22:25 -08:00
|
|
|
|
Improve git behavior in the zero- and one- commit case
Summary:
Git works completely differently for commits zero and one than for 2..N so add
more special casing to handle them. See:
- {T206}
- {T596}
The getCommitRange() block is also fatal land, although I wasn't able to reach
it. I'll follow up with @s on T596.
Test Plan:
- Created a new, empty repository ("mkdir x; cd x; git init").
- Ran "arc lint", "arc unit", "arc diff" against it with no commits (the first
two work, the third fails helpfully).
- Made an initial commit.
- Ran "arc lint", "arc unit", "arc diff" against it (all work correctly).
Reviewers: btrahan, jungejason, aran
Reviewed By: aran
CC: s, aran
Differential Revision: 1142
2011-11-30 09:15:37 -08:00
|
|
|
private $repositoryHasNoCommits = false;
|
2011-01-09 15:22:25 -08:00
|
|
|
const SEARCH_LENGTH_FOR_PARENT_REVISIONS = 16;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* For the repository's initial commit, 'git diff HEAD^' and similar do
|
2012-12-17 12:53:28 -08:00
|
|
|
* not work. Using this instead does work; it is the hash of the empty tree.
|
2011-01-09 15:22:25 -08:00
|
|
|
*/
|
|
|
|
const GIT_MAGIC_ROOT_COMMIT = '4b825dc642cb6eb9a060e54bf8d69288fbee4904';
|
|
|
|
|
|
|
|
public static function newHookAPI($root) {
|
|
|
|
return new ArcanistGitAPI($root);
|
|
|
|
}
|
|
|
|
|
2012-03-02 16:47:34 -08:00
|
|
|
protected function buildLocalFuture(array $argv) {
|
|
|
|
|
|
|
|
$argv[0] = 'git '.$argv[0];
|
|
|
|
|
|
|
|
$future = newv('ExecFuture', $argv);
|
|
|
|
$future->setCWD($this->getPath());
|
|
|
|
return $future;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-01-09 15:22:25 -08:00
|
|
|
public function getSourceControlSystemName() {
|
|
|
|
return 'git';
|
|
|
|
}
|
|
|
|
|
2012-06-12 12:39:15 -07:00
|
|
|
public function getMetadataPath() {
|
2013-01-17 11:28:13 -08:00
|
|
|
static $path = null;
|
|
|
|
if ($path === null) {
|
|
|
|
list($stdout) = $this->execxLocal('rev-parse --git-dir');
|
|
|
|
$path = rtrim($stdout, "\n");
|
|
|
|
// the output of git rev-parse --git-dir is an absolute path, unless
|
|
|
|
// the cwd is the root of the repository, in which case it uses the
|
|
|
|
// relative path of .git. If we get this relative path, turn it into
|
|
|
|
// an absolute path.
|
|
|
|
if ($path === '.git') {
|
|
|
|
$path = $this->getPath('.git');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $path;
|
2012-06-12 12:39:15 -07:00
|
|
|
}
|
|
|
|
|
Improve git behavior in the zero- and one- commit case
Summary:
Git works completely differently for commits zero and one than for 2..N so add
more special casing to handle them. See:
- {T206}
- {T596}
The getCommitRange() block is also fatal land, although I wasn't able to reach
it. I'll follow up with @s on T596.
Test Plan:
- Created a new, empty repository ("mkdir x; cd x; git init").
- Ran "arc lint", "arc unit", "arc diff" against it with no commits (the first
two work, the third fails helpfully).
- Made an initial commit.
- Ran "arc lint", "arc unit", "arc diff" against it (all work correctly).
Reviewers: btrahan, jungejason, aran
Reviewed By: aran
CC: s, aran
Differential Revision: 1142
2011-11-30 09:15:37 -08:00
|
|
|
public function getHasCommits() {
|
|
|
|
return !$this->repositoryHasNoCommits;
|
|
|
|
}
|
|
|
|
|
2011-08-23 18:48:55 -07:00
|
|
|
public function getLocalCommitInformation() {
|
Improve git behavior in the zero- and one- commit case
Summary:
Git works completely differently for commits zero and one than for 2..N so add
more special casing to handle them. See:
- {T206}
- {T596}
The getCommitRange() block is also fatal land, although I wasn't able to reach
it. I'll follow up with @s on T596.
Test Plan:
- Created a new, empty repository ("mkdir x; cd x; git init").
- Ran "arc lint", "arc unit", "arc diff" against it with no commits (the first
two work, the third fails helpfully).
- Made an initial commit.
- Ran "arc lint", "arc unit", "arc diff" against it (all work correctly).
Reviewers: btrahan, jungejason, aran
Reviewed By: aran
CC: s, aran
Differential Revision: 1142
2011-11-30 09:15:37 -08:00
|
|
|
if ($this->repositoryHasNoCommits) {
|
|
|
|
// Zero commits.
|
|
|
|
throw new Exception(
|
|
|
|
"You can't get local commit information for a repository with no ".
|
|
|
|
"commits.");
|
2012-12-17 12:54:08 -08:00
|
|
|
} else if ($this->getBaseCommit() == self::GIT_MAGIC_ROOT_COMMIT) {
|
Improve git behavior in the zero- and one- commit case
Summary:
Git works completely differently for commits zero and one than for 2..N so add
more special casing to handle them. See:
- {T206}
- {T596}
The getCommitRange() block is also fatal land, although I wasn't able to reach
it. I'll follow up with @s on T596.
Test Plan:
- Created a new, empty repository ("mkdir x; cd x; git init").
- Ran "arc lint", "arc unit", "arc diff" against it with no commits (the first
two work, the third fails helpfully).
- Made an initial commit.
- Ran "arc lint", "arc unit", "arc diff" against it (all work correctly).
Reviewers: btrahan, jungejason, aran
Reviewed By: aran
CC: s, aran
Differential Revision: 1142
2011-11-30 09:15:37 -08:00
|
|
|
// One commit.
|
|
|
|
$against = 'HEAD';
|
|
|
|
} else {
|
2012-05-11 13:52:14 -07:00
|
|
|
|
|
|
|
// 2..N commits. We include commits reachable from HEAD which are
|
|
|
|
// not reachable from the relative commit; this is consistent with
|
|
|
|
// user expectations even though it is not actually the diff range.
|
|
|
|
// Particularly:
|
|
|
|
//
|
|
|
|
// |
|
|
|
|
// D <----- master branch
|
|
|
|
// |
|
|
|
|
// C Y <- feature branch
|
|
|
|
// | /|
|
|
|
|
// B X
|
|
|
|
// | /
|
|
|
|
// A
|
|
|
|
// |
|
|
|
|
//
|
|
|
|
// If "A, B, C, D" are master, and the user is at Y, when they run
|
|
|
|
// "arc diff B" they want (and get) a diff of B vs Y, but they think about
|
|
|
|
// this as being the commits X and Y. If we log "B..Y", we only show
|
|
|
|
// Y. With "Y --not B", we show X and Y.
|
|
|
|
|
2012-12-17 12:54:08 -08:00
|
|
|
$against = csprintf('%s --not %s', 'HEAD', $this->getBaseCommit());
|
Improve git behavior in the zero- and one- commit case
Summary:
Git works completely differently for commits zero and one than for 2..N so add
more special casing to handle them. See:
- {T206}
- {T596}
The getCommitRange() block is also fatal land, although I wasn't able to reach
it. I'll follow up with @s on T596.
Test Plan:
- Created a new, empty repository ("mkdir x; cd x; git init").
- Ran "arc lint", "arc unit", "arc diff" against it with no commits (the first
two work, the third fails helpfully).
- Made an initial commit.
- Ran "arc lint", "arc unit", "arc diff" against it (all work correctly).
Reviewers: btrahan, jungejason, aran
Reviewed By: aran
CC: s, aran
Differential Revision: 1142
2011-11-30 09:15:37 -08:00
|
|
|
}
|
|
|
|
|
Fix escaping of "git log ---format" command on Windows
Summary:
On Windows, the PHP function escapeshellarg() replaces '%' with ' ' (space). This is apparently because there is no safe way to escape % inside of strings.
cmd.exe does use "^" as an escape character, so I think replacing "xyz" with "^x^y^z" might work for arbitrary strings (maybe?), or at least some subset of strings, but I don't know cmd.exe well enough to make that call without being concerned I'm introducing a security issue.
Although this patch is dumb, it's certinaly safe, and can only do something wrong if the user has environmental variables like H, P, T, or x01, in which case they're sort of asking for it.
cmd.exe also truncates output on \0, so use \1 as a delimiter instead.
Seriously it's like this thing was written in 1982 and never ever changed.
Test Plan: Created D1783 successfully after applying this patch.
Reviewers: btrahan
Reviewed By: btrahan
CC: aran, epriestley
Maniphest Tasks: T124
Differential Revision: https://secure.phabricator.com/D1785
2012-03-05 13:22:41 -08:00
|
|
|
// NOTE: Windows escaping of "%" symbols apparently is inherently broken;
|
|
|
|
// when passed throuhgh escapeshellarg() they are replaced with spaces.
|
|
|
|
|
|
|
|
// TODO: Learn how cmd.exe works and find some clever workaround?
|
|
|
|
|
|
|
|
// NOTE: If we use "%x00", output is truncated in Windows.
|
|
|
|
|
2012-03-02 16:47:34 -08:00
|
|
|
list($info) = $this->execxLocal(
|
Fix escaping of "git log ---format" command on Windows
Summary:
On Windows, the PHP function escapeshellarg() replaces '%' with ' ' (space). This is apparently because there is no safe way to escape % inside of strings.
cmd.exe does use "^" as an escape character, so I think replacing "xyz" with "^x^y^z" might work for arbitrary strings (maybe?), or at least some subset of strings, but I don't know cmd.exe well enough to make that call without being concerned I'm introducing a security issue.
Although this patch is dumb, it's certinaly safe, and can only do something wrong if the user has environmental variables like H, P, T, or x01, in which case they're sort of asking for it.
cmd.exe also truncates output on \0, so use \1 as a delimiter instead.
Seriously it's like this thing was written in 1982 and never ever changed.
Test Plan: Created D1783 successfully after applying this patch.
Reviewers: btrahan
Reviewed By: btrahan
CC: aran, epriestley
Maniphest Tasks: T124
Differential Revision: https://secure.phabricator.com/D1785
2012-03-05 13:22:41 -08:00
|
|
|
phutil_is_windows()
|
2012-05-11 13:52:14 -07:00
|
|
|
? 'log %C --format=%C --'
|
|
|
|
: 'log %C --format=%s --',
|
Improve git behavior in the zero- and one- commit case
Summary:
Git works completely differently for commits zero and one than for 2..N so add
more special casing to handle them. See:
- {T206}
- {T596}
The getCommitRange() block is also fatal land, although I wasn't able to reach
it. I'll follow up with @s on T596.
Test Plan:
- Created a new, empty repository ("mkdir x; cd x; git init").
- Ran "arc lint", "arc unit", "arc diff" against it with no commits (the first
two work, the third fails helpfully).
- Made an initial commit.
- Ran "arc lint", "arc unit", "arc diff" against it (all work correctly).
Reviewers: btrahan, jungejason, aran
Reviewed By: aran
CC: s, aran
Differential Revision: 1142
2011-11-30 09:15:37 -08:00
|
|
|
$against,
|
2012-05-09 15:56:16 -07:00
|
|
|
// NOTE: "%B" is somewhat new, use "%s%n%n%b" instead.
|
|
|
|
'%H%x01%T%x01%P%x01%at%x01%an%x01%s%x01%s%n%n%b%x02');
|
2011-08-23 18:48:55 -07:00
|
|
|
|
|
|
|
$commits = array();
|
|
|
|
|
2012-05-09 15:56:16 -07:00
|
|
|
$info = trim($info, " \n\2");
|
2012-05-11 06:07:33 -07:00
|
|
|
if (!strlen($info)) {
|
|
|
|
return array();
|
|
|
|
}
|
|
|
|
|
2012-05-09 15:56:16 -07:00
|
|
|
$info = explode("\2", $info);
|
2011-08-23 18:48:55 -07:00
|
|
|
foreach ($info as $line) {
|
2012-05-09 15:56:16 -07:00
|
|
|
list($commit, $tree, $parents, $time, $author, $title, $message)
|
2012-05-11 13:52:14 -07:00
|
|
|
= explode("\1", trim($line), 7);
|
2012-05-09 15:56:16 -07:00
|
|
|
$message = rtrim($message);
|
2011-08-23 18:48:55 -07:00
|
|
|
|
2012-05-09 15:56:16 -07:00
|
|
|
$commits[$commit] = array(
|
2011-08-23 18:48:55 -07:00
|
|
|
'commit' => $commit,
|
|
|
|
'tree' => $tree,
|
|
|
|
'parents' => array_filter(explode(' ', $parents)),
|
|
|
|
'time' => $time,
|
|
|
|
'author' => $author,
|
|
|
|
'summary' => $title,
|
2012-05-09 15:56:16 -07:00
|
|
|
'message' => $message,
|
2011-08-23 18:48:55 -07:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $commits;
|
|
|
|
}
|
|
|
|
|
2012-12-17 12:54:08 -08:00
|
|
|
protected function buildBaseCommit($symbolic_commit) {
|
|
|
|
if ($symbolic_commit !== null) {
|
|
|
|
if ($symbolic_commit == ArcanistGitAPI::GIT_MAGIC_ROOT_COMMIT) {
|
|
|
|
$this->setBaseCommitExplanation(
|
|
|
|
"you explicitly specified the empty tree.");
|
|
|
|
return $symbolic_commit;
|
|
|
|
}
|
Revenge of the git relative commit default
Summary:
The default of "arc diff" to "arc diff HEAD^" in git is universally confusing to everyone not at Facebook.
Drive the default with configuration instead. Even at Facebook, "origin/trunk" (or whatever) is probably a better default than "HEAD^".
See D863 for the last attempt at this.
NOTE: This is contentious!
Test Plan: Ran "arc diff", got prompted to set a default. Ran "arc diff" from a zero-commit repo, got sensible behavior
Reviewers: btrahan, vrana, nh, jungejason, tuomaspelkonen
Reviewed By: btrahan
CC: aran, epriestley, zeeg, davidreuss
Maniphest Tasks: T651
Differential Revision: https://secure.phabricator.com/D1861
2012-04-02 16:52:20 -07:00
|
|
|
|
2012-12-17 12:54:08 -08:00
|
|
|
list($err, $merge_base) = $this->execManualLocal(
|
|
|
|
'merge-base %s HEAD',
|
|
|
|
$symbolic_commit);
|
2011-01-09 15:22:25 -08:00
|
|
|
if ($err) {
|
2012-12-17 12:54:08 -08:00
|
|
|
throw new ArcanistUsageException(
|
|
|
|
"Unable to find any git commit named '{$symbolic_commit}' in ".
|
|
|
|
"this repository.");
|
Revenge of the git relative commit default
Summary:
The default of "arc diff" to "arc diff HEAD^" in git is universally confusing to everyone not at Facebook.
Drive the default with configuration instead. Even at Facebook, "origin/trunk" (or whatever) is probably a better default than "HEAD^".
See D863 for the last attempt at this.
NOTE: This is contentious!
Test Plan: Ran "arc diff", got prompted to set a default. Ran "arc diff" from a zero-commit repo, got sensible behavior
Reviewers: btrahan, vrana, nh, jungejason, tuomaspelkonen
Reviewed By: btrahan
CC: aran, epriestley, zeeg, davidreuss
Maniphest Tasks: T651
Differential Revision: https://secure.phabricator.com/D1861
2012-04-02 16:52:20 -07:00
|
|
|
}
|
|
|
|
|
2012-12-17 12:54:08 -08:00
|
|
|
$this->setBaseCommitExplanation(
|
|
|
|
"it is the merge-base of '{$symbolic_commit}' and HEAD, as you ".
|
|
|
|
"explicitly specified.");
|
|
|
|
return trim($merge_base);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Detect zero-commit or one-commit repositories. There is only one
|
|
|
|
// relative-commit value that makes any sense in these repositories: the
|
|
|
|
// empty tree.
|
|
|
|
list($err) = $this->execManualLocal('rev-parse --verify HEAD^');
|
|
|
|
if ($err) {
|
|
|
|
list($err) = $this->execManualLocal('rev-parse --verify HEAD');
|
|
|
|
if ($err) {
|
|
|
|
$this->repositoryHasNoCommits = true;
|
Add a DSL for selecting base commits
Summary:
New optional mode. If you set 'base' in local, project or global config or pass '--base' to 'arc diff' or 'arc which', it switches to DSL mode.
In DSL mode, lists of rules from args, local, project and global config are resolved, in that order. Rules can manipulate the rule machine or resolve into actual commits. Provides support for some 'arc' rules (mostly machine manipulation) and 'git' rules (symbolic ref and merge-base).
Test Plan:
Ran unit tests. Also:
```$ arc which --show-base --base 'arc:prompt'
Against which commit? HEAD
HEAD
$ arc which --show-base --base 'git:HEAD'
HEAD
$ arc which --show-base --base 'git:fake'
Usage Exception: None of the rules in your 'base' configuration matched a valid commit. Adjust rules or specify which commit you want to use explicitly.
$ arc which --show-base --base 'git:origin/master'
origin/master
$ arc which --show-base --base 'git:upstream'
Usage Exception: None of the rules in your 'base' configuration matched a valid commit. Adjust rules or specify which commit you want to use explicitly.
$ arc which --show-base --base 'literal:derp'
derp
$ arc which --show-base --base 'arc:halt'
Usage Exception: None of the rules in your 'base' configuration matched a valid commit. Adjust rules or specify which commit you want to use explicitly.
$ arc set-config --local base git:origin/master
Set key 'base' = 'git:origin/master' in local config.
$ arc which --show-base
origin/master
$ arc which --show-base --base 'git:HEAD^'
HEAD^
$ arc which --show-base --base 'arc:yield, git:HEAD^'
origin/master
$ arc which --show-base --base 'arc:global, git:HEAD^'
HEAD^
$ arc which --show-base --base 'arc:global, git:merge-base(origin/master)'
3f4f8992fba8d1f142974da36a82bae900e247c0```
Reviewers: dschleimer, vrana
Reviewed By: dschleimer
CC: aran
Maniphest Tasks: T1233
Differential Revision: https://secure.phabricator.com/D2748
2012-06-15 14:01:28 -07:00
|
|
|
}
|
|
|
|
|
2012-12-17 12:54:08 -08:00
|
|
|
if ($this->repositoryHasNoCommits) {
|
2012-06-14 12:02:41 -07:00
|
|
|
$this->setBaseCommitExplanation(
|
2012-12-17 12:54:08 -08:00
|
|
|
"the repository has no commits.");
|
|
|
|
} else {
|
|
|
|
$this->setBaseCommitExplanation(
|
|
|
|
"the repository has only one commit.");
|
2012-05-06 17:03:32 -07:00
|
|
|
}
|
2012-04-10 15:33:31 -07:00
|
|
|
|
2012-12-17 12:54:08 -08:00
|
|
|
return self::GIT_MAGIC_ROOT_COMMIT;
|
|
|
|
}
|
2012-04-10 15:33:31 -07:00
|
|
|
|
2012-12-17 12:54:08 -08:00
|
|
|
if ($this->getBaseCommitArgumentRules() ||
|
|
|
|
$this->getWorkingCopyIdentity()->getConfigFromAnySource('base')) {
|
|
|
|
$base = $this->resolveBaseCommit();
|
|
|
|
if (!$base) {
|
|
|
|
throw new ArcanistUsageException(
|
|
|
|
"None of the rules in your 'base' configuration matched a valid ".
|
|
|
|
"commit. Adjust rules or specify which commit you want to use ".
|
|
|
|
"explicitly.");
|
2012-04-10 15:33:31 -07:00
|
|
|
}
|
2012-12-17 12:54:08 -08:00
|
|
|
return $base;
|
|
|
|
}
|
2012-04-10 15:33:31 -07:00
|
|
|
|
2012-12-17 12:54:08 -08:00
|
|
|
$do_write = false;
|
|
|
|
$default_relative = null;
|
|
|
|
$working_copy = $this->getWorkingCopyIdentity();
|
|
|
|
if ($working_copy) {
|
|
|
|
$default_relative = $working_copy->getConfig(
|
|
|
|
'git.default-relative-commit');
|
|
|
|
$this->setBaseCommitExplanation(
|
|
|
|
"it is the merge-base of '{$default_relative}' and HEAD, as ".
|
|
|
|
"specified in 'git.default-relative-commit' in '.arcconfig'. This ".
|
|
|
|
"setting overrides other settings.");
|
|
|
|
}
|
2012-04-03 16:06:43 -07:00
|
|
|
|
2012-12-17 12:54:08 -08:00
|
|
|
if (!$default_relative) {
|
|
|
|
list($err, $upstream) = $this->execManualLocal(
|
|
|
|
"rev-parse --abbrev-ref --symbolic-full-name '@{upstream}'");
|
Revenge of the git relative commit default
Summary:
The default of "arc diff" to "arc diff HEAD^" in git is universally confusing to everyone not at Facebook.
Drive the default with configuration instead. Even at Facebook, "origin/trunk" (or whatever) is probably a better default than "HEAD^".
See D863 for the last attempt at this.
NOTE: This is contentious!
Test Plan: Ran "arc diff", got prompted to set a default. Ran "arc diff" from a zero-commit repo, got sensible behavior
Reviewers: btrahan, vrana, nh, jungejason, tuomaspelkonen
Reviewed By: btrahan
CC: aran, epriestley, zeeg, davidreuss
Maniphest Tasks: T651
Differential Revision: https://secure.phabricator.com/D1861
2012-04-02 16:52:20 -07:00
|
|
|
|
2012-12-17 12:54:08 -08:00
|
|
|
if (!$err) {
|
|
|
|
$default_relative = trim($upstream);
|
|
|
|
$this->setBaseCommitExplanation(
|
|
|
|
"it is the merge-base of '{$default_relative}' (the Git upstream ".
|
|
|
|
"of the current branch) HEAD.");
|
Revenge of the git relative commit default
Summary:
The default of "arc diff" to "arc diff HEAD^" in git is universally confusing to everyone not at Facebook.
Drive the default with configuration instead. Even at Facebook, "origin/trunk" (or whatever) is probably a better default than "HEAD^".
See D863 for the last attempt at this.
NOTE: This is contentious!
Test Plan: Ran "arc diff", got prompted to set a default. Ran "arc diff" from a zero-commit repo, got sensible behavior
Reviewers: btrahan, vrana, nh, jungejason, tuomaspelkonen
Reviewed By: btrahan
CC: aran, epriestley, zeeg, davidreuss
Maniphest Tasks: T651
Differential Revision: https://secure.phabricator.com/D1861
2012-04-02 16:52:20 -07:00
|
|
|
}
|
2012-12-17 12:54:08 -08:00
|
|
|
}
|
Revenge of the git relative commit default
Summary:
The default of "arc diff" to "arc diff HEAD^" in git is universally confusing to everyone not at Facebook.
Drive the default with configuration instead. Even at Facebook, "origin/trunk" (or whatever) is probably a better default than "HEAD^".
See D863 for the last attempt at this.
NOTE: This is contentious!
Test Plan: Ran "arc diff", got prompted to set a default. Ran "arc diff" from a zero-commit repo, got sensible behavior
Reviewers: btrahan, vrana, nh, jungejason, tuomaspelkonen
Reviewed By: btrahan
CC: aran, epriestley, zeeg, davidreuss
Maniphest Tasks: T651
Differential Revision: https://secure.phabricator.com/D1861
2012-04-02 16:52:20 -07:00
|
|
|
|
2012-12-17 12:54:08 -08:00
|
|
|
if (!$default_relative) {
|
|
|
|
$default_relative = $this->readScratchFile('default-relative-commit');
|
|
|
|
$default_relative = trim($default_relative);
|
|
|
|
if ($default_relative) {
|
|
|
|
$this->setBaseCommitExplanation(
|
|
|
|
"it is the merge-base of '{$default_relative}' and HEAD, as ".
|
|
|
|
"specified in '.git/arc/default-relative-commit'.");
|
Revenge of the git relative commit default
Summary:
The default of "arc diff" to "arc diff HEAD^" in git is universally confusing to everyone not at Facebook.
Drive the default with configuration instead. Even at Facebook, "origin/trunk" (or whatever) is probably a better default than "HEAD^".
See D863 for the last attempt at this.
NOTE: This is contentious!
Test Plan: Ran "arc diff", got prompted to set a default. Ran "arc diff" from a zero-commit repo, got sensible behavior
Reviewers: btrahan, vrana, nh, jungejason, tuomaspelkonen
Reviewed By: btrahan
CC: aran, epriestley, zeeg, davidreuss
Maniphest Tasks: T651
Differential Revision: https://secure.phabricator.com/D1861
2012-04-02 16:52:20 -07:00
|
|
|
}
|
2012-12-17 12:54:08 -08:00
|
|
|
}
|
Revenge of the git relative commit default
Summary:
The default of "arc diff" to "arc diff HEAD^" in git is universally confusing to everyone not at Facebook.
Drive the default with configuration instead. Even at Facebook, "origin/trunk" (or whatever) is probably a better default than "HEAD^".
See D863 for the last attempt at this.
NOTE: This is contentious!
Test Plan: Ran "arc diff", got prompted to set a default. Ran "arc diff" from a zero-commit repo, got sensible behavior
Reviewers: btrahan, vrana, nh, jungejason, tuomaspelkonen
Reviewed By: btrahan
CC: aran, epriestley, zeeg, davidreuss
Maniphest Tasks: T651
Differential Revision: https://secure.phabricator.com/D1861
2012-04-02 16:52:20 -07:00
|
|
|
|
2012-12-17 12:54:08 -08:00
|
|
|
if (!$default_relative) {
|
|
|
|
|
|
|
|
// TODO: Remove the history lesson soon.
|
|
|
|
|
|
|
|
echo phutil_console_format(
|
|
|
|
"<bg:green>** Select a Default Commit Range **</bg>\n\n");
|
|
|
|
echo phutil_console_wrap(
|
|
|
|
"You're running a command which operates on a range of revisions ".
|
|
|
|
"(usually, from some revision to HEAD) but have not specified the ".
|
|
|
|
"revision that should determine the start of the range.\n\n".
|
|
|
|
"Previously, arc assumed you meant 'HEAD^' when you did not specify ".
|
|
|
|
"a start revision, but this behavior does not make much sense in ".
|
|
|
|
"most workflows outside of Facebook's historic git-svn workflow.\n\n".
|
|
|
|
"arc no longer assumes 'HEAD^'. You must specify a relative commit ".
|
|
|
|
"explicitly when you invoke a command (e.g., `arc diff HEAD^`, not ".
|
|
|
|
"just `arc diff`) or select a default for this working copy.\n\n".
|
|
|
|
"In most cases, the best default is 'origin/master'. You can also ".
|
|
|
|
"select 'HEAD^' to preserve the old behavior, or some other remote ".
|
|
|
|
"or branch. But you almost certainly want to select ".
|
|
|
|
"'origin/master'.\n\n".
|
|
|
|
"(Technically: the merge-base of the selected revision and HEAD is ".
|
|
|
|
"used to determine the start of the commit range.)");
|
|
|
|
|
|
|
|
$prompt = "What default do you want to use? [origin/master]";
|
|
|
|
$default = phutil_console_prompt($prompt);
|
|
|
|
|
|
|
|
if (!strlen(trim($default))) {
|
|
|
|
$default = 'origin/master';
|
2011-01-09 15:22:25 -08:00
|
|
|
}
|
Revenge of the git relative commit default
Summary:
The default of "arc diff" to "arc diff HEAD^" in git is universally confusing to everyone not at Facebook.
Drive the default with configuration instead. Even at Facebook, "origin/trunk" (or whatever) is probably a better default than "HEAD^".
See D863 for the last attempt at this.
NOTE: This is contentious!
Test Plan: Ran "arc diff", got prompted to set a default. Ran "arc diff" from a zero-commit repo, got sensible behavior
Reviewers: btrahan, vrana, nh, jungejason, tuomaspelkonen
Reviewed By: btrahan
CC: aran, epriestley, zeeg, davidreuss
Maniphest Tasks: T651
Differential Revision: https://secure.phabricator.com/D1861
2012-04-02 16:52:20 -07:00
|
|
|
|
2012-12-17 12:54:08 -08:00
|
|
|
$default_relative = $default;
|
|
|
|
$do_write = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
list($object_type) = $this->execxLocal(
|
|
|
|
'cat-file -t %s',
|
|
|
|
$default_relative);
|
Revenge of the git relative commit default
Summary:
The default of "arc diff" to "arc diff HEAD^" in git is universally confusing to everyone not at Facebook.
Drive the default with configuration instead. Even at Facebook, "origin/trunk" (or whatever) is probably a better default than "HEAD^".
See D863 for the last attempt at this.
NOTE: This is contentious!
Test Plan: Ran "arc diff", got prompted to set a default. Ran "arc diff" from a zero-commit repo, got sensible behavior
Reviewers: btrahan, vrana, nh, jungejason, tuomaspelkonen
Reviewed By: btrahan
CC: aran, epriestley, zeeg, davidreuss
Maniphest Tasks: T651
Differential Revision: https://secure.phabricator.com/D1861
2012-04-02 16:52:20 -07:00
|
|
|
|
2012-12-17 12:54:08 -08:00
|
|
|
if (trim($object_type) !== 'commit') {
|
|
|
|
throw new Exception(
|
|
|
|
"Relative commit '{$default_relative}' is not the name of a commit!");
|
2011-01-09 15:22:25 -08:00
|
|
|
}
|
Revenge of the git relative commit default
Summary:
The default of "arc diff" to "arc diff HEAD^" in git is universally confusing to everyone not at Facebook.
Drive the default with configuration instead. Even at Facebook, "origin/trunk" (or whatever) is probably a better default than "HEAD^".
See D863 for the last attempt at this.
NOTE: This is contentious!
Test Plan: Ran "arc diff", got prompted to set a default. Ran "arc diff" from a zero-commit repo, got sensible behavior
Reviewers: btrahan, vrana, nh, jungejason, tuomaspelkonen
Reviewed By: btrahan
CC: aran, epriestley, zeeg, davidreuss
Maniphest Tasks: T651
Differential Revision: https://secure.phabricator.com/D1861
2012-04-02 16:52:20 -07:00
|
|
|
|
2012-12-17 12:54:08 -08:00
|
|
|
if ($do_write) {
|
|
|
|
// Don't perform this write until we've verified that the object is a
|
|
|
|
// valid commit name.
|
|
|
|
$this->writeScratchFile('default-relative-commit', $default_relative);
|
|
|
|
$this->setBaseCommitExplanation(
|
|
|
|
"it is the merge-base of '{$default_relative}' and HEAD, as you ".
|
|
|
|
"just specified.");
|
|
|
|
}
|
|
|
|
|
|
|
|
list($merge_base) = $this->execxLocal(
|
|
|
|
'merge-base %s HEAD',
|
|
|
|
$default_relative);
|
|
|
|
|
|
|
|
return trim($merge_base);
|
2011-01-09 15:22:25 -08:00
|
|
|
}
|
|
|
|
|
2012-11-15 15:47:43 -08:00
|
|
|
private function getDiffFullOptions($detect_moves_and_renames = true) {
|
|
|
|
$options = array(
|
2011-05-29 10:45:18 -07:00
|
|
|
self::getDiffBaseOptions(),
|
2011-01-09 15:22:25 -08:00
|
|
|
'--no-color',
|
|
|
|
'--src-prefix=a/',
|
|
|
|
'--dst-prefix=b/',
|
|
|
|
'-U'.$this->getDiffLinesOfContext(),
|
|
|
|
);
|
2012-04-30 16:47:12 -07:00
|
|
|
|
|
|
|
if ($detect_moves_and_renames) {
|
2012-11-15 15:47:43 -08:00
|
|
|
$options[] = '-M';
|
|
|
|
$options[] = '-C';
|
2012-04-30 16:47:12 -07:00
|
|
|
}
|
|
|
|
|
2012-11-15 15:47:43 -08:00
|
|
|
return implode(' ', $options);
|
2011-01-09 15:22:25 -08:00
|
|
|
}
|
|
|
|
|
2011-05-29 10:45:18 -07:00
|
|
|
private function getDiffBaseOptions() {
|
|
|
|
$options = array(
|
|
|
|
// Disable external diff drivers, like graphical differs, since Arcanist
|
|
|
|
// needs to capture the diff text.
|
|
|
|
'--no-ext-diff',
|
|
|
|
// Disable textconv so we treat binary files as binary, even if they have
|
|
|
|
// an alternative textual representation. TODO: Ideally, Differential
|
|
|
|
// would ship up the binaries for 'arc patch' but display the textconv
|
|
|
|
// output in the visual diff.
|
|
|
|
'--no-textconv',
|
|
|
|
);
|
|
|
|
return implode(' ', $options);
|
|
|
|
}
|
|
|
|
|
2011-01-09 15:22:25 -08:00
|
|
|
public function getFullGitDiff() {
|
2012-11-15 15:47:43 -08:00
|
|
|
$options = $this->getDiffFullOptions();
|
2012-03-02 16:47:34 -08:00
|
|
|
list($stdout) = $this->execxLocal(
|
2012-11-15 15:47:43 -08:00
|
|
|
"diff {$options} %s --",
|
2012-12-17 12:54:08 -08:00
|
|
|
$this->getBaseCommit());
|
2011-01-09 15:22:25 -08:00
|
|
|
return $stdout;
|
|
|
|
}
|
|
|
|
|
2012-04-30 16:47:12 -07:00
|
|
|
/**
|
|
|
|
* @param string Path to generate a diff for.
|
|
|
|
* @param bool If true, detect moves and renames. Otherwise, ignore
|
|
|
|
* moves/renames; this is useful because it prompts git to
|
|
|
|
* generate real diff text.
|
|
|
|
*/
|
|
|
|
public function getRawDiffText($path, $detect_moves_and_renames = true) {
|
2012-11-15 15:47:43 -08:00
|
|
|
$options = $this->getDiffFullOptions($detect_moves_and_renames);
|
2012-03-02 16:47:34 -08:00
|
|
|
list($stdout) = $this->execxLocal(
|
2012-11-15 15:47:43 -08:00
|
|
|
"diff {$options} %s -- %s",
|
2012-12-17 12:54:08 -08:00
|
|
|
$this->getBaseCommit(),
|
2011-01-09 15:22:25 -08:00
|
|
|
$path);
|
|
|
|
return $stdout;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getBranchName() {
|
|
|
|
// TODO: consider:
|
|
|
|
//
|
|
|
|
// $ git rev-parse --abbrev-ref `git symbolic-ref HEAD`
|
|
|
|
//
|
|
|
|
// But that may fail if you're not on a branch.
|
2012-09-13 15:12:19 -07:00
|
|
|
list($stdout) = $this->execxLocal('branch --no-color');
|
2011-01-09 15:22:25 -08:00
|
|
|
|
Support --nobranch when not currently on a branch
Summary:
This is horrible and git specific, but fixes a case where people are using "arc
patch --nobranch ..." when they're not currently on a branch.
The old code assumed you were on a branch and used getBranchName() to record
this, in order to return to that branch later and cherry-pick the patch.
When not on a branch, and using arc patch --nobranch, this was trying to return
to the branch '(no branch)'.
Now, I detect that we're not on a branch and just record what HEAD is instead.
Test Plan:
Checkout the SHA of master (so I'm on master, but not on a branch) then try to
patch it with a feature diff:
€ git checkout e7a3ec68159d6847372cab5ad913f2f15aa7c249
Warning: you are leaving 1 commit behind, not connected to
any of your branches:
ac1ad39 Updating a-file
If you want to keep them by creating a new branch, this may be a good time
to do so with:
git branch new_branch_name ac1ad392350a51edd10343f12b9713f5e5b3707c
HEAD is now at e7a3ec6... Fix arcconfig
€ arc patch --nobranch D7
Created and checked out branch arcpatch-D7.
OKAY Successfully committed patch.
€ git branch
* (no branch)
feature
haddock
master
€ git log --oneline | head -2
38c0235 Updating a-file
e7a3ec6 Fix arcconfig
Reviewers: vrana, epriestley
Reviewed By: epriestley
CC: aran, Korvin
Differential Revision: https://secure.phabricator.com/D3743
2012-10-19 12:12:57 -07:00
|
|
|
// Assume that any branch beginning with '(' means 'no branch', or whatever
|
|
|
|
// 'no branch' is in the current locale.
|
2011-01-09 15:22:25 -08:00
|
|
|
$matches = null;
|
Support --nobranch when not currently on a branch
Summary:
This is horrible and git specific, but fixes a case where people are using "arc
patch --nobranch ..." when they're not currently on a branch.
The old code assumed you were on a branch and used getBranchName() to record
this, in order to return to that branch later and cherry-pick the patch.
When not on a branch, and using arc patch --nobranch, this was trying to return
to the branch '(no branch)'.
Now, I detect that we're not on a branch and just record what HEAD is instead.
Test Plan:
Checkout the SHA of master (so I'm on master, but not on a branch) then try to
patch it with a feature diff:
€ git checkout e7a3ec68159d6847372cab5ad913f2f15aa7c249
Warning: you are leaving 1 commit behind, not connected to
any of your branches:
ac1ad39 Updating a-file
If you want to keep them by creating a new branch, this may be a good time
to do so with:
git branch new_branch_name ac1ad392350a51edd10343f12b9713f5e5b3707c
HEAD is now at e7a3ec6... Fix arcconfig
€ arc patch --nobranch D7
Created and checked out branch arcpatch-D7.
OKAY Successfully committed patch.
€ git branch
* (no branch)
feature
haddock
master
€ git log --oneline | head -2
38c0235 Updating a-file
e7a3ec6 Fix arcconfig
Reviewers: vrana, epriestley
Reviewed By: epriestley
CC: aran, Korvin
Differential Revision: https://secure.phabricator.com/D3743
2012-10-19 12:12:57 -07:00
|
|
|
if (preg_match('/^\* ([^\(].*)$/m', $stdout, $matches)) {
|
2011-01-09 15:22:25 -08:00
|
|
|
return $matches[1];
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getSourceControlPath() {
|
|
|
|
// TODO: Try to get something useful here.
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getGitCommitLog() {
|
2012-12-17 12:54:08 -08:00
|
|
|
$relative = $this->getBaseCommit();
|
Improve git behavior in the zero- and one- commit case
Summary:
Git works completely differently for commits zero and one than for 2..N so add
more special casing to handle them. See:
- {T206}
- {T596}
The getCommitRange() block is also fatal land, although I wasn't able to reach
it. I'll follow up with @s on T596.
Test Plan:
- Created a new, empty repository ("mkdir x; cd x; git init").
- Ran "arc lint", "arc unit", "arc diff" against it with no commits (the first
two work, the third fails helpfully).
- Made an initial commit.
- Ran "arc lint", "arc unit", "arc diff" against it (all work correctly).
Reviewers: btrahan, jungejason, aran
Reviewed By: aran
CC: s, aran
Differential Revision: 1142
2011-11-30 09:15:37 -08:00
|
|
|
if ($this->repositoryHasNoCommits) {
|
|
|
|
// No commits yet.
|
|
|
|
return '';
|
|
|
|
} else if ($relative == self::GIT_MAGIC_ROOT_COMMIT) {
|
|
|
|
// First commit.
|
2012-04-30 14:14:23 -07:00
|
|
|
list($stdout) = $this->execxLocal(
|
2012-04-30 17:28:04 -07:00
|
|
|
'log --format=medium HEAD');
|
2011-01-09 15:22:25 -08:00
|
|
|
} else {
|
Improve git behavior in the zero- and one- commit case
Summary:
Git works completely differently for commits zero and one than for 2..N so add
more special casing to handle them. See:
- {T206}
- {T596}
The getCommitRange() block is also fatal land, although I wasn't able to reach
it. I'll follow up with @s on T596.
Test Plan:
- Created a new, empty repository ("mkdir x; cd x; git init").
- Ran "arc lint", "arc unit", "arc diff" against it with no commits (the first
two work, the third fails helpfully).
- Made an initial commit.
- Ran "arc lint", "arc unit", "arc diff" against it (all work correctly).
Reviewers: btrahan, jungejason, aran
Reviewed By: aran
CC: s, aran
Differential Revision: 1142
2011-11-30 09:15:37 -08:00
|
|
|
// 2..N commits.
|
2012-03-02 16:47:34 -08:00
|
|
|
list($stdout) = $this->execxLocal(
|
2012-04-30 17:28:04 -07:00
|
|
|
'log --first-parent --format=medium %s..HEAD',
|
2012-12-17 12:54:08 -08:00
|
|
|
$this->getBaseCommit());
|
2011-01-09 15:22:25 -08:00
|
|
|
}
|
|
|
|
return $stdout;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getGitHistoryLog() {
|
2012-03-02 16:47:34 -08:00
|
|
|
list($stdout) = $this->execxLocal(
|
|
|
|
'log --format=medium -n%d %s',
|
2011-01-09 15:22:25 -08:00
|
|
|
self::SEARCH_LENGTH_FOR_PARENT_REVISIONS,
|
2012-12-17 12:54:08 -08:00
|
|
|
$this->getBaseCommit());
|
2011-01-09 15:22:25 -08:00
|
|
|
return $stdout;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getSourceControlBaseRevision() {
|
2012-03-02 16:47:34 -08:00
|
|
|
list($stdout) = $this->execxLocal(
|
|
|
|
'rev-parse %s',
|
2012-12-17 12:54:08 -08:00
|
|
|
$this->getBaseCommit());
|
2011-01-09 15:22:25 -08:00
|
|
|
return rtrim($stdout, "\n");
|
|
|
|
}
|
|
|
|
|
2012-03-09 14:40:47 -08:00
|
|
|
public function getCanonicalRevisionName($string) {
|
2012-09-21 14:02:27 -07:00
|
|
|
$match = null;
|
|
|
|
if (preg_match('/@([0-9]+)$/', $string, $match)) {
|
|
|
|
list($stdout) = $this->execxLocal(
|
|
|
|
'svn find-rev r%d',
|
|
|
|
$match[1]);
|
|
|
|
} else {
|
|
|
|
list($stdout) = $this->execxLocal(
|
|
|
|
'show -s --format=%C %s',
|
|
|
|
'%H',
|
|
|
|
$string);
|
|
|
|
}
|
2012-03-09 14:40:47 -08:00
|
|
|
return rtrim($stdout);
|
2011-01-09 15:22:25 -08:00
|
|
|
}
|
|
|
|
|
2012-12-17 12:53:28 -08:00
|
|
|
protected function buildUncommittedStatus() {
|
|
|
|
$diff_options = $this->getDiffBaseOptions();
|
2011-07-05 11:06:46 -07:00
|
|
|
|
2012-12-17 12:53:28 -08:00
|
|
|
if ($this->repositoryHasNoCommits) {
|
|
|
|
$diff_base = self::GIT_MAGIC_ROOT_COMMIT;
|
|
|
|
} else {
|
|
|
|
$diff_base = 'HEAD';
|
|
|
|
}
|
2011-07-05 11:06:46 -07:00
|
|
|
|
2012-12-17 12:53:28 -08:00
|
|
|
// Find uncommitted changes.
|
|
|
|
$uncommitted_future = $this->buildLocalFuture(
|
|
|
|
array(
|
|
|
|
'diff %C --raw %s --',
|
|
|
|
$diff_options,
|
|
|
|
$diff_base,
|
|
|
|
));
|
|
|
|
|
|
|
|
$untracked_future = $this->buildLocalFuture(
|
|
|
|
array(
|
|
|
|
'ls-files --others --exclude-standard',
|
|
|
|
));
|
|
|
|
|
|
|
|
// TODO: This doesn't list unstaged adds. It's not clear how to get that
|
|
|
|
// list other than "git status --porcelain" and then parsing it. :/
|
|
|
|
|
|
|
|
// Unstaged changes
|
|
|
|
$unstaged_future = $this->buildLocalFuture(
|
|
|
|
array(
|
|
|
|
'ls-files -m',
|
|
|
|
));
|
|
|
|
|
|
|
|
$futures = array(
|
|
|
|
$uncommitted_future,
|
|
|
|
$untracked_future,
|
|
|
|
$unstaged_future,
|
|
|
|
);
|
2011-07-05 11:06:46 -07:00
|
|
|
|
2012-12-17 12:53:28 -08:00
|
|
|
Futures($futures)->resolveAll();
|
2011-07-05 11:06:46 -07:00
|
|
|
|
2012-12-17 12:53:28 -08:00
|
|
|
$result = new PhutilArrayWithDefaultValue();
|
2011-01-09 15:22:25 -08:00
|
|
|
|
2012-12-17 12:53:28 -08:00
|
|
|
list($stdout) = $uncommitted_future->resolvex();
|
|
|
|
$uncommitted_files = $this->parseGitStatus($stdout);
|
|
|
|
foreach ($uncommitted_files as $path => $mask) {
|
|
|
|
$result[$path] |= ($mask | self::FLAG_UNCOMMITTED);
|
|
|
|
}
|
2011-01-09 15:22:25 -08:00
|
|
|
|
2012-12-17 12:53:28 -08:00
|
|
|
list($stdout) = $untracked_future->resolvex();
|
|
|
|
$stdout = rtrim($stdout, "\n");
|
|
|
|
if (strlen($stdout)) {
|
|
|
|
$stdout = explode("\n", $stdout);
|
|
|
|
foreach ($stdout as $path) {
|
|
|
|
$result[$path] |= self::FLAG_UNTRACKED;
|
2011-01-09 15:22:25 -08:00
|
|
|
}
|
2012-12-17 12:53:28 -08:00
|
|
|
}
|
2011-01-09 15:22:25 -08:00
|
|
|
|
2012-12-17 12:53:28 -08:00
|
|
|
list($stdout, $stderr) = $unstaged_future->resolvex();
|
|
|
|
$stdout = rtrim($stdout, "\n");
|
|
|
|
if (strlen($stdout)) {
|
|
|
|
$stdout = explode("\n", $stdout);
|
|
|
|
foreach ($stdout as $path) {
|
|
|
|
$result[$path] |= self::FLAG_UNSTAGED;
|
|
|
|
}
|
2011-01-09 15:22:25 -08:00
|
|
|
}
|
|
|
|
|
2012-12-17 12:53:28 -08:00
|
|
|
return $result->toArray();
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function buildCommitRangeStatus() {
|
|
|
|
list($stdout, $stderr) = $this->execxLocal(
|
|
|
|
'diff %C --raw %s --',
|
|
|
|
$this->getDiffBaseOptions(),
|
2012-12-17 12:54:08 -08:00
|
|
|
$this->getBaseCommit());
|
2012-12-17 12:53:28 -08:00
|
|
|
|
|
|
|
return $this->parseGitStatus($stdout);
|
2011-01-09 15:22:25 -08:00
|
|
|
}
|
|
|
|
|
2012-11-20 10:43:19 -08:00
|
|
|
public function getGitConfig($key, $default = null) {
|
2012-11-29 15:35:57 -08:00
|
|
|
list($err, $stdout) = $this->execManualLocal('config %s', $key);
|
|
|
|
if ($err) {
|
2012-11-20 10:43:19 -08:00
|
|
|
return $default;
|
|
|
|
}
|
|
|
|
return rtrim($stdout);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getAuthor() {
|
2012-11-29 15:35:57 -08:00
|
|
|
list($stdout) = $this->execxLocal('var GIT_AUTHOR_IDENT');
|
|
|
|
return preg_replace('/\s+<.*/', '', rtrim($stdout, "\n"));
|
2012-11-20 10:43:19 -08:00
|
|
|
}
|
|
|
|
|
2012-11-15 12:33:36 -08:00
|
|
|
public function addToCommit(array $paths) {
|
|
|
|
$this->execxLocal(
|
2013-01-23 13:53:30 -08:00
|
|
|
'add -A -- %Ls',
|
2012-11-15 12:33:36 -08:00
|
|
|
$paths);
|
2012-12-17 12:54:08 -08:00
|
|
|
$this->reloadWorkingCopy();
|
|
|
|
return $this;
|
2012-11-15 12:33:36 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
public function doCommit($message) {
|
|
|
|
$tmp_file = new TempFile();
|
|
|
|
Filesystem::writeFile($tmp_file, $message);
|
|
|
|
$this->execxLocal(
|
|
|
|
'commit --allow-empty-message -F %s',
|
|
|
|
$tmp_file);
|
2012-12-17 12:53:28 -08:00
|
|
|
|
|
|
|
$this->reloadWorkingCopy();
|
|
|
|
|
|
|
|
return $this;
|
2012-11-15 12:33:36 -08:00
|
|
|
}
|
|
|
|
|
2012-06-04 15:30:50 -04:00
|
|
|
public function amendCommit($message) {
|
2012-05-02 12:39:12 -07:00
|
|
|
$tmp_file = new TempFile();
|
|
|
|
Filesystem::writeFile($tmp_file, $message);
|
2012-03-02 16:47:34 -08:00
|
|
|
$this->execxLocal(
|
2012-05-02 12:39:12 -07:00
|
|
|
'commit --amend --allow-empty -F %s',
|
|
|
|
$tmp_file);
|
2012-12-17 12:54:08 -08:00
|
|
|
$this->reloadWorkingCopy();
|
|
|
|
return $this;
|
2011-01-09 15:22:25 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
public function getPreReceiveHookStatus($old_ref, $new_ref) {
|
2011-05-29 10:45:18 -07:00
|
|
|
$options = $this->getDiffBaseOptions();
|
2012-03-02 16:47:34 -08:00
|
|
|
list($stdout) = $this->execxLocal(
|
|
|
|
"diff {$options} --raw %s %s --",
|
2011-01-09 15:22:25 -08:00
|
|
|
$old_ref,
|
|
|
|
$new_ref);
|
|
|
|
return $this->parseGitStatus($stdout, $full = true);
|
|
|
|
}
|
|
|
|
|
|
|
|
private function parseGitStatus($status, $full = false) {
|
|
|
|
static $flags = array(
|
|
|
|
'A' => self::FLAG_ADDED,
|
|
|
|
'M' => self::FLAG_MODIFIED,
|
|
|
|
'D' => self::FLAG_DELETED,
|
|
|
|
);
|
|
|
|
|
|
|
|
$status = trim($status);
|
|
|
|
$lines = array();
|
|
|
|
foreach (explode("\n", $status) as $line) {
|
|
|
|
if ($line) {
|
2012-11-08 19:29:40 -08:00
|
|
|
$lines[] = preg_split("/[ \t]/", $line, 6);
|
2011-01-09 15:22:25 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$files = array();
|
|
|
|
foreach ($lines as $line) {
|
|
|
|
$mask = 0;
|
|
|
|
$flag = $line[4];
|
|
|
|
$file = $line[5];
|
|
|
|
foreach ($flags as $key => $bits) {
|
|
|
|
if ($flag == $key) {
|
|
|
|
$mask |= $bits;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ($full) {
|
|
|
|
$files[$file] = array(
|
|
|
|
'mask' => $mask,
|
|
|
|
'ref' => rtrim($line[3], '.'),
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
$files[$file] = $mask;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $files;
|
|
|
|
}
|
|
|
|
|
2012-11-08 23:22:29 -08:00
|
|
|
public function getAllFiles() {
|
|
|
|
$future = $this->buildLocalFuture(array('ls-files -z'));
|
|
|
|
return id(new LinesOfALargeExecFuture($future))
|
|
|
|
->setDelimiter("\0");
|
|
|
|
}
|
|
|
|
|
2012-11-08 23:26:27 -08:00
|
|
|
public function getChangedFiles($since_commit) {
|
|
|
|
list($stdout) = $this->execxLocal(
|
2012-11-21 15:57:06 -08:00
|
|
|
'diff --raw %s',
|
2012-11-08 23:26:27 -08:00
|
|
|
$since_commit);
|
2012-11-08 19:29:40 -08:00
|
|
|
return $this->parseGitStatus($stdout);
|
2012-11-08 23:26:27 -08:00
|
|
|
}
|
|
|
|
|
2011-01-09 15:22:25 -08:00
|
|
|
public function getBlame($path) {
|
|
|
|
// TODO: 'git blame' supports --porcelain and we should probably use it.
|
2012-03-02 16:47:34 -08:00
|
|
|
list($stdout) = $this->execxLocal(
|
|
|
|
'blame --date=iso -w -M %s -- %s',
|
2012-12-17 12:54:08 -08:00
|
|
|
$this->getBaseCommit(),
|
2011-01-09 15:22:25 -08:00
|
|
|
$path);
|
|
|
|
|
|
|
|
$blame = array();
|
|
|
|
foreach (explode("\n", trim($stdout)) as $line) {
|
|
|
|
if (!strlen($line)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// lines predating a git repo's history are blamed to the oldest revision,
|
|
|
|
// with the commit hash prepended by a ^. we shouldn't count these lines
|
|
|
|
// as blaming to the oldest diff's unfortunate author
|
|
|
|
if ($line[0] == '^') {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$matches = null;
|
|
|
|
$ok = preg_match(
|
|
|
|
'/^([0-9a-f]+)[^(]+?[(](.*?) +\d\d\d\d-\d\d-\d\d/',
|
|
|
|
$line,
|
|
|
|
$matches);
|
|
|
|
if (!$ok) {
|
|
|
|
throw new Exception("Bad blame? `{$line}'");
|
|
|
|
}
|
|
|
|
$revision = $matches[1];
|
|
|
|
$author = $matches[2];
|
|
|
|
|
|
|
|
$blame[] = array($author, $revision);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $blame;
|
|
|
|
}
|
|
|
|
|
2011-01-11 13:02:38 -08:00
|
|
|
public function getOriginalFileData($path) {
|
2012-12-17 12:54:08 -08:00
|
|
|
return $this->getFileDataAtRevision($path, $this->getBaseCommit());
|
2011-01-11 13:02:38 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
public function getCurrentFileData($path) {
|
|
|
|
return $this->getFileDataAtRevision($path, 'HEAD');
|
|
|
|
}
|
|
|
|
|
|
|
|
private function parseGitTree($stdout) {
|
|
|
|
$result = array();
|
|
|
|
|
|
|
|
$stdout = trim($stdout);
|
|
|
|
if (!strlen($stdout)) {
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
|
|
|
$lines = explode("\n", $stdout);
|
|
|
|
foreach ($lines as $line) {
|
|
|
|
$matches = array();
|
|
|
|
$ok = preg_match(
|
|
|
|
'/^(\d{6}) (blob|tree) ([a-z0-9]{40})[\t](.*)$/',
|
|
|
|
$line,
|
|
|
|
$matches);
|
|
|
|
if (!$ok) {
|
|
|
|
throw new Exception("Failed to parse git ls-tree output!");
|
|
|
|
}
|
|
|
|
$result[$matches[4]] = array(
|
|
|
|
'mode' => $matches[1],
|
|
|
|
'type' => $matches[2],
|
|
|
|
'ref' => $matches[3],
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
|
|
|
private function getFileDataAtRevision($path, $revision) {
|
|
|
|
|
|
|
|
// NOTE: We don't want to just "git show {$revision}:{$path}" since if the
|
|
|
|
// path was a directory at the given revision we'll get a list of its files
|
|
|
|
// and treat it as though it as a file containing a list of other files,
|
|
|
|
// which is silly.
|
|
|
|
|
2012-03-02 16:47:34 -08:00
|
|
|
list($stdout) = $this->execxLocal(
|
|
|
|
'ls-tree %s -- %s',
|
2011-01-11 13:02:38 -08:00
|
|
|
$revision,
|
|
|
|
$path);
|
|
|
|
|
|
|
|
$info = $this->parseGitTree($stdout);
|
|
|
|
if (empty($info[$path])) {
|
|
|
|
// No such path, or the path is a directory and we executed 'ls-tree dir/'
|
|
|
|
// and got a list of its contents back.
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($info[$path]['type'] != 'blob') {
|
|
|
|
// Path is or was a directory, not a file.
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2012-03-02 16:47:34 -08:00
|
|
|
list($stdout) = $this->execxLocal(
|
|
|
|
'cat-file blob %s',
|
2011-01-11 13:02:38 -08:00
|
|
|
$info[$path]['ref']);
|
|
|
|
return $stdout;
|
|
|
|
}
|
|
|
|
|
2011-06-23 12:10:59 -07:00
|
|
|
/**
|
|
|
|
* Returns names of all the branches in the current repository.
|
|
|
|
*
|
2012-06-26 10:50:43 -07:00
|
|
|
* @return list<dict<string, string>> Dictionary of branch information.
|
2011-06-23 12:10:59 -07:00
|
|
|
*/
|
|
|
|
public function getAllBranches() {
|
2012-06-26 10:50:43 -07:00
|
|
|
list($branch_info) = $this->execxLocal(
|
2012-07-18 16:50:49 -07:00
|
|
|
'branch --no-color');
|
2012-06-26 10:50:43 -07:00
|
|
|
$lines = explode("\n", rtrim($branch_info));
|
|
|
|
|
2011-06-23 12:10:59 -07:00
|
|
|
$result = array();
|
|
|
|
foreach ($lines as $line) {
|
2012-06-26 10:50:43 -07:00
|
|
|
|
|
|
|
if (preg_match('/^[* ]+\(no branch\)/', $line)) {
|
|
|
|
// This is indicating that the working copy is in a detached state;
|
|
|
|
// just ignore it.
|
2011-06-29 13:21:30 -07:00
|
|
|
continue;
|
|
|
|
}
|
2012-06-26 10:50:43 -07:00
|
|
|
|
2012-07-18 16:50:49 -07:00
|
|
|
list($current, $name) = preg_split('/\s+/', $line, 2);
|
2011-06-29 13:21:30 -07:00
|
|
|
$result[] = array(
|
2012-06-26 10:50:43 -07:00
|
|
|
'current' => !empty($current),
|
2011-06-29 13:21:30 -07:00
|
|
|
'name' => $name,
|
|
|
|
);
|
2011-06-23 12:10:59 -07:00
|
|
|
}
|
2012-03-02 16:47:34 -08:00
|
|
|
|
2012-06-26 10:50:43 -07:00
|
|
|
return $result;
|
2011-06-23 12:10:59 -07:00
|
|
|
}
|
|
|
|
|
2011-12-02 16:21:14 -08:00
|
|
|
public function getWorkingCopyRevision() {
|
2012-03-02 16:47:34 -08:00
|
|
|
list($stdout) = $this->execxLocal('rev-parse HEAD');
|
2011-12-02 16:21:14 -08:00
|
|
|
return rtrim($stdout, "\n");
|
|
|
|
}
|
|
|
|
|
2012-11-29 17:40:07 -08:00
|
|
|
public function getUnderlyingWorkingCopyRevision() {
|
|
|
|
list($err, $stdout) = $this->execManualLocal('svn find-rev HEAD');
|
|
|
|
if (!$err && $stdout) {
|
|
|
|
return rtrim($stdout, "\n");
|
|
|
|
}
|
|
|
|
return $this->getWorkingCopyRevision();
|
|
|
|
}
|
|
|
|
|
2012-06-04 15:30:50 -04:00
|
|
|
public function isHistoryDefaultImmutable() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function supportsAmend() {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2012-12-17 12:54:08 -08:00
|
|
|
public function supportsCommitRanges() {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function supportsLocalCommits() {
|
2011-09-14 18:44:54 -07:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2012-03-07 13:02:53 -08:00
|
|
|
public function hasLocalCommit($commit) {
|
2012-03-09 14:40:47 -08:00
|
|
|
try {
|
2012-09-21 14:02:27 -07:00
|
|
|
if (!$this->getCanonicalRevisionName($commit)) {
|
|
|
|
return false;
|
|
|
|
}
|
2012-03-09 14:40:47 -08:00
|
|
|
} catch (CommandException $exception) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
2012-03-07 13:02:53 -08:00
|
|
|
}
|
|
|
|
|
2011-09-14 18:44:54 -07:00
|
|
|
public function getAllLocalChanges() {
|
|
|
|
$diff = $this->getFullGitDiff();
|
2012-02-29 12:18:46 -08:00
|
|
|
if (!strlen(trim($diff))) {
|
|
|
|
return array();
|
|
|
|
}
|
2011-09-14 18:44:54 -07:00
|
|
|
$parser = new ArcanistDiffParser();
|
|
|
|
return $parser->parseDiff($diff);
|
|
|
|
}
|
|
|
|
|
Add an "arc merge" workflow
Summary:
This should support conservative rewrite policies in git fairly well, under an
assumed workflow of:
- Develop in local branches, never rewrite history.
- Commit with "-m" or by typing a brief, non-template commit message
describing the checkpoint.
- Provide rich information in the web console (reviewers, etc.)
- Finalize with "git checkout master && arc merge branch && git push" or some
flavor thereof.
This supports Mercurial somewhat. The major problem is that "hg merge" fails if
the local is a fastforward of the remote, at which point there's nowhere we can
throw the commit message. Oh well. Just push it and we'll do our best to link
them up based on local commit info.
I am increasingly forming an opinion that Mercurial is "saftey-scissors git".
But also maybe I have no clue what I'm doing. I just don't understand why anyone
would think it's a good idea to have a trunk consisting of ~50% known-broken
revisions, random checkpoint parts, whitespace changes, typo fixes, etc. If you
use git with branching you can avoid this by making a trunk out of merges or
with rebase/amend, but there seems to be no way to have "one commit = one idea"
in any real sense in Mercurial.
Test Plan: Execute "arc merge" in git and mercurial.
Reviewers: fratrik, Makinde, aran, jungejason, tuomaspelkonen
Reviewed By: Makinde
CC: aran, epriestley, Makinde
Differential Revision: 860
2011-08-25 16:02:03 -07:00
|
|
|
public function supportsLocalBranchMerge() {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function performLocalBranchMerge($branch, $message) {
|
|
|
|
if (!$branch) {
|
|
|
|
throw new ArcanistUsageException(
|
|
|
|
"Under git, you must specify the branch you want to merge.");
|
|
|
|
}
|
|
|
|
$err = phutil_passthru(
|
|
|
|
'(cd %s && git merge --no-ff -m %s %s)',
|
|
|
|
$this->getPath(),
|
|
|
|
$message,
|
|
|
|
$branch);
|
|
|
|
|
|
|
|
if ($err) {
|
|
|
|
throw new ArcanistUsageException("Merge failed!");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getFinalizedRevisionMessage() {
|
|
|
|
return "You may now push this commit upstream, as appropriate (e.g. with ".
|
|
|
|
"'git push', or 'git svn dcommit', or by printing and faxing it).";
|
|
|
|
}
|
|
|
|
|
2012-07-01 11:06:05 -07:00
|
|
|
public function getCommitMessage($commit) {
|
2012-03-02 16:47:34 -08:00
|
|
|
list($message) = $this->execxLocal(
|
2012-07-01 11:06:05 -07:00
|
|
|
'log -n1 --format=%C %s --',
|
2012-09-06 08:56:44 -06:00
|
|
|
'%s%n%n%b',
|
2012-07-01 11:06:05 -07:00
|
|
|
$commit);
|
|
|
|
return $message;
|
2012-01-12 19:03:11 -08:00
|
|
|
}
|
|
|
|
|
2012-01-24 08:07:38 -08:00
|
|
|
public function loadWorkingCopyDifferentialRevisions(
|
|
|
|
ConduitClient $conduit,
|
|
|
|
array $query) {
|
|
|
|
|
|
|
|
$messages = $this->getGitCommitLog();
|
|
|
|
if (!strlen($messages)) {
|
|
|
|
return array();
|
|
|
|
}
|
|
|
|
|
|
|
|
$parser = new ArcanistDiffParser();
|
|
|
|
$messages = $parser->parseDiff($messages);
|
|
|
|
|
|
|
|
// First, try to find revisions by explicit revision IDs in commit messages.
|
2012-05-11 06:07:33 -07:00
|
|
|
$reason_map = array();
|
2012-01-24 08:07:38 -08:00
|
|
|
$revision_ids = array();
|
|
|
|
foreach ($messages as $message) {
|
|
|
|
$object = ArcanistDifferentialCommitMessage::newFromRawCorpus(
|
|
|
|
$message->getMetadata('message'));
|
|
|
|
if ($object->getRevisionID()) {
|
|
|
|
$revision_ids[] = $object->getRevisionID();
|
2012-05-11 06:07:33 -07:00
|
|
|
$reason_map[$object->getRevisionID()] = $message->getCommitHash();
|
2012-01-24 08:07:38 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($revision_ids) {
|
|
|
|
$results = $conduit->callMethodSynchronous(
|
|
|
|
'differential.query',
|
|
|
|
$query + array(
|
|
|
|
'ids' => $revision_ids,
|
|
|
|
));
|
2012-05-11 06:07:33 -07:00
|
|
|
|
|
|
|
foreach ($results as $key => $result) {
|
|
|
|
$hash = substr($reason_map[$result['id']], 0, 16);
|
|
|
|
$results[$key]['why'] =
|
|
|
|
"Commit message for '{$hash}' has explicit 'Differential Revision'.";
|
|
|
|
}
|
|
|
|
|
2012-01-24 08:07:38 -08:00
|
|
|
return $results;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we didn't succeed, try to find revisions by hash.
|
|
|
|
$hashes = array();
|
|
|
|
foreach ($this->getLocalCommitInformation() as $commit) {
|
|
|
|
$hashes[] = array('gtcm', $commit['commit']);
|
|
|
|
$hashes[] = array('gttr', $commit['tree']);
|
|
|
|
}
|
|
|
|
|
|
|
|
$results = $conduit->callMethodSynchronous(
|
|
|
|
'differential.query',
|
|
|
|
$query + array(
|
|
|
|
'commitHashes' => $hashes,
|
|
|
|
));
|
|
|
|
|
2012-05-11 06:07:33 -07:00
|
|
|
foreach ($results as $key => $result) {
|
|
|
|
$results[$key]['why'] =
|
|
|
|
"A git commit or tree hash in the commit range is already attached ".
|
|
|
|
"to the Differential revision.";
|
|
|
|
}
|
|
|
|
|
2012-01-24 08:07:38 -08:00
|
|
|
return $results;
|
|
|
|
}
|
|
|
|
|
2012-03-16 13:40:33 -07:00
|
|
|
public function updateWorkingCopy() {
|
|
|
|
$this->execxLocal('pull');
|
2012-12-17 12:54:08 -08:00
|
|
|
$this->reloadWorkingCopy();
|
2012-03-16 13:40:33 -07:00
|
|
|
}
|
|
|
|
|
2012-05-11 06:07:33 -07:00
|
|
|
public function getCommitSummary($commit) {
|
|
|
|
if ($commit == self::GIT_MAGIC_ROOT_COMMIT) {
|
|
|
|
return '(The Empty Tree)';
|
|
|
|
}
|
|
|
|
|
|
|
|
list($summary) = $this->execxLocal(
|
|
|
|
'log -n 1 --format=%C %s',
|
|
|
|
'%s',
|
|
|
|
$commit);
|
|
|
|
|
|
|
|
return trim($summary);
|
|
|
|
}
|
|
|
|
|
Add a DSL for selecting base commits
Summary:
New optional mode. If you set 'base' in local, project or global config or pass '--base' to 'arc diff' or 'arc which', it switches to DSL mode.
In DSL mode, lists of rules from args, local, project and global config are resolved, in that order. Rules can manipulate the rule machine or resolve into actual commits. Provides support for some 'arc' rules (mostly machine manipulation) and 'git' rules (symbolic ref and merge-base).
Test Plan:
Ran unit tests. Also:
```$ arc which --show-base --base 'arc:prompt'
Against which commit? HEAD
HEAD
$ arc which --show-base --base 'git:HEAD'
HEAD
$ arc which --show-base --base 'git:fake'
Usage Exception: None of the rules in your 'base' configuration matched a valid commit. Adjust rules or specify which commit you want to use explicitly.
$ arc which --show-base --base 'git:origin/master'
origin/master
$ arc which --show-base --base 'git:upstream'
Usage Exception: None of the rules in your 'base' configuration matched a valid commit. Adjust rules or specify which commit you want to use explicitly.
$ arc which --show-base --base 'literal:derp'
derp
$ arc which --show-base --base 'arc:halt'
Usage Exception: None of the rules in your 'base' configuration matched a valid commit. Adjust rules or specify which commit you want to use explicitly.
$ arc set-config --local base git:origin/master
Set key 'base' = 'git:origin/master' in local config.
$ arc which --show-base
origin/master
$ arc which --show-base --base 'git:HEAD^'
HEAD^
$ arc which --show-base --base 'arc:yield, git:HEAD^'
origin/master
$ arc which --show-base --base 'arc:global, git:HEAD^'
HEAD^
$ arc which --show-base --base 'arc:global, git:merge-base(origin/master)'
3f4f8992fba8d1f142974da36a82bae900e247c0```
Reviewers: dschleimer, vrana
Reviewed By: dschleimer
CC: aran
Maniphest Tasks: T1233
Differential Revision: https://secure.phabricator.com/D2748
2012-06-15 14:01:28 -07:00
|
|
|
public function resolveBaseCommitRule($rule, $source) {
|
|
|
|
list($type, $name) = explode(':', $rule, 2);
|
|
|
|
|
|
|
|
switch ($type) {
|
|
|
|
case 'git':
|
|
|
|
$matches = null;
|
|
|
|
if (preg_match('/^merge-base\((.+)\)$/', $name, $matches)) {
|
|
|
|
list($err, $merge_base) = $this->execManualLocal(
|
|
|
|
'merge-base %s HEAD',
|
|
|
|
$matches[1]);
|
|
|
|
if (!$err) {
|
|
|
|
$this->setBaseCommitExplanation(
|
|
|
|
"it is the merge-base of '{$matches[1]}' and HEAD, as ".
|
|
|
|
"specified by '{$rule}' in your {$source} 'base' ".
|
|
|
|
"configuration.");
|
|
|
|
return trim($merge_base);
|
|
|
|
}
|
2012-06-27 12:12:39 -07:00
|
|
|
} else if (preg_match('/^branch-unique\((.+)\)$/', $name, $matches)) {
|
|
|
|
list($err, $merge_base) = $this->execManualLocal(
|
|
|
|
'merge-base %s HEAD',
|
|
|
|
$matches[1]);
|
|
|
|
if ($err) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
$merge_base = trim($merge_base);
|
|
|
|
|
|
|
|
list($commits) = $this->execxLocal(
|
|
|
|
'log --format=%C %s..HEAD --',
|
|
|
|
'%H',
|
|
|
|
$merge_base);
|
|
|
|
$commits = array_filter(explode("\n", $commits));
|
|
|
|
|
|
|
|
if (!$commits) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
$commits[] = $merge_base;
|
|
|
|
|
|
|
|
$head_branch_count = null;
|
|
|
|
foreach ($commits as $commit) {
|
|
|
|
list($branches) = $this->execxLocal(
|
|
|
|
'branch --contains %s',
|
|
|
|
$commit);
|
|
|
|
$branches = array_filter(explode("\n", $branches));
|
|
|
|
if ($head_branch_count === null) {
|
|
|
|
// If this is the first commit, it's HEAD. Count how many
|
|
|
|
// branches it is on; we want to include commits on the same
|
|
|
|
// number of branches. This covers a case where this branch
|
|
|
|
// has sub-branches and we're running "arc diff" here again
|
|
|
|
// for whatever reason.
|
|
|
|
$head_branch_count = count($branches);
|
|
|
|
} else if (count($branches) > $head_branch_count) {
|
|
|
|
foreach ($branches as $key => $branch) {
|
|
|
|
$branches[$key] = trim($branch, ' *');
|
|
|
|
}
|
|
|
|
$branches = implode(', ', $branches);
|
|
|
|
$this->setBaseCommitExplanation(
|
|
|
|
"it is the first commit between '{$merge_base}' (the ".
|
|
|
|
"merge-base of '{$matches[1]}' and HEAD) which is also ".
|
|
|
|
"contained by another branch ({$branches}).");
|
|
|
|
return $commit;
|
|
|
|
}
|
|
|
|
}
|
Add a DSL for selecting base commits
Summary:
New optional mode. If you set 'base' in local, project or global config or pass '--base' to 'arc diff' or 'arc which', it switches to DSL mode.
In DSL mode, lists of rules from args, local, project and global config are resolved, in that order. Rules can manipulate the rule machine or resolve into actual commits. Provides support for some 'arc' rules (mostly machine manipulation) and 'git' rules (symbolic ref and merge-base).
Test Plan:
Ran unit tests. Also:
```$ arc which --show-base --base 'arc:prompt'
Against which commit? HEAD
HEAD
$ arc which --show-base --base 'git:HEAD'
HEAD
$ arc which --show-base --base 'git:fake'
Usage Exception: None of the rules in your 'base' configuration matched a valid commit. Adjust rules or specify which commit you want to use explicitly.
$ arc which --show-base --base 'git:origin/master'
origin/master
$ arc which --show-base --base 'git:upstream'
Usage Exception: None of the rules in your 'base' configuration matched a valid commit. Adjust rules or specify which commit you want to use explicitly.
$ arc which --show-base --base 'literal:derp'
derp
$ arc which --show-base --base 'arc:halt'
Usage Exception: None of the rules in your 'base' configuration matched a valid commit. Adjust rules or specify which commit you want to use explicitly.
$ arc set-config --local base git:origin/master
Set key 'base' = 'git:origin/master' in local config.
$ arc which --show-base
origin/master
$ arc which --show-base --base 'git:HEAD^'
HEAD^
$ arc which --show-base --base 'arc:yield, git:HEAD^'
origin/master
$ arc which --show-base --base 'arc:global, git:HEAD^'
HEAD^
$ arc which --show-base --base 'arc:global, git:merge-base(origin/master)'
3f4f8992fba8d1f142974da36a82bae900e247c0```
Reviewers: dschleimer, vrana
Reviewed By: dschleimer
CC: aran
Maniphest Tasks: T1233
Differential Revision: https://secure.phabricator.com/D2748
2012-06-15 14:01:28 -07:00
|
|
|
} else {
|
|
|
|
list($err) = $this->execManualLocal(
|
|
|
|
'cat-file -t %s',
|
|
|
|
$name);
|
|
|
|
if (!$err) {
|
|
|
|
$this->setBaseCommitExplanation(
|
|
|
|
"it is specified by '{$rule}' in your {$source} 'base' ".
|
|
|
|
"configuration.");
|
|
|
|
return $name;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'arc':
|
|
|
|
switch ($name) {
|
|
|
|
case 'empty':
|
|
|
|
$this->setBaseCommitExplanation(
|
|
|
|
"you specified '{$rule}' in your {$source} 'base' ".
|
|
|
|
"configuration.");
|
|
|
|
return self::GIT_MAGIC_ROOT_COMMIT;
|
2012-07-01 11:06:05 -07:00
|
|
|
case 'amended':
|
|
|
|
$text = $this->getCommitMessage('HEAD');
|
|
|
|
$message = ArcanistDifferentialCommitMessage::newFromRawCorpus(
|
|
|
|
$text);
|
|
|
|
if ($message->getRevisionID()) {
|
|
|
|
$this->setBaseCommitExplanation(
|
|
|
|
"HEAD has been amended with 'Differential Revision:', ".
|
|
|
|
"as specified by '{$rule}' in your {$source} 'base' ".
|
|
|
|
"configuration.");
|
|
|
|
return 'HEAD^';
|
|
|
|
}
|
|
|
|
break;
|
Add a DSL for selecting base commits
Summary:
New optional mode. If you set 'base' in local, project or global config or pass '--base' to 'arc diff' or 'arc which', it switches to DSL mode.
In DSL mode, lists of rules from args, local, project and global config are resolved, in that order. Rules can manipulate the rule machine or resolve into actual commits. Provides support for some 'arc' rules (mostly machine manipulation) and 'git' rules (symbolic ref and merge-base).
Test Plan:
Ran unit tests. Also:
```$ arc which --show-base --base 'arc:prompt'
Against which commit? HEAD
HEAD
$ arc which --show-base --base 'git:HEAD'
HEAD
$ arc which --show-base --base 'git:fake'
Usage Exception: None of the rules in your 'base' configuration matched a valid commit. Adjust rules or specify which commit you want to use explicitly.
$ arc which --show-base --base 'git:origin/master'
origin/master
$ arc which --show-base --base 'git:upstream'
Usage Exception: None of the rules in your 'base' configuration matched a valid commit. Adjust rules or specify which commit you want to use explicitly.
$ arc which --show-base --base 'literal:derp'
derp
$ arc which --show-base --base 'arc:halt'
Usage Exception: None of the rules in your 'base' configuration matched a valid commit. Adjust rules or specify which commit you want to use explicitly.
$ arc set-config --local base git:origin/master
Set key 'base' = 'git:origin/master' in local config.
$ arc which --show-base
origin/master
$ arc which --show-base --base 'git:HEAD^'
HEAD^
$ arc which --show-base --base 'arc:yield, git:HEAD^'
origin/master
$ arc which --show-base --base 'arc:global, git:HEAD^'
HEAD^
$ arc which --show-base --base 'arc:global, git:merge-base(origin/master)'
3f4f8992fba8d1f142974da36a82bae900e247c0```
Reviewers: dschleimer, vrana
Reviewed By: dschleimer
CC: aran
Maniphest Tasks: T1233
Differential Revision: https://secure.phabricator.com/D2748
2012-06-15 14:01:28 -07:00
|
|
|
case 'upstream':
|
|
|
|
list($err, $upstream) = $this->execManualLocal(
|
|
|
|
"rev-parse --abbrev-ref --symbolic-full-name '@{upstream}'");
|
|
|
|
if (!$err) {
|
|
|
|
list($upstream_merge_base) = $this->execxLocal(
|
|
|
|
'merge-base %s HEAD',
|
|
|
|
$upstream);
|
|
|
|
$this->setBaseCommitExplanation(
|
|
|
|
"it is the merge-base of the upstream of the current branch ".
|
|
|
|
"and HEAD, and matched the rule '{$rule}' in your {$source} ".
|
|
|
|
"'base' configuration.");
|
|
|
|
return $upstream_merge_base;
|
|
|
|
}
|
|
|
|
break;
|
2012-12-17 12:53:38 -08:00
|
|
|
case 'this':
|
|
|
|
$this->setBaseCommitExplanation(
|
|
|
|
"you specified '{$rule}' in your {$source} 'base' ".
|
|
|
|
"configuration.");
|
|
|
|
return 'HEAD^';
|
Add a DSL for selecting base commits
Summary:
New optional mode. If you set 'base' in local, project or global config or pass '--base' to 'arc diff' or 'arc which', it switches to DSL mode.
In DSL mode, lists of rules from args, local, project and global config are resolved, in that order. Rules can manipulate the rule machine or resolve into actual commits. Provides support for some 'arc' rules (mostly machine manipulation) and 'git' rules (symbolic ref and merge-base).
Test Plan:
Ran unit tests. Also:
```$ arc which --show-base --base 'arc:prompt'
Against which commit? HEAD
HEAD
$ arc which --show-base --base 'git:HEAD'
HEAD
$ arc which --show-base --base 'git:fake'
Usage Exception: None of the rules in your 'base' configuration matched a valid commit. Adjust rules or specify which commit you want to use explicitly.
$ arc which --show-base --base 'git:origin/master'
origin/master
$ arc which --show-base --base 'git:upstream'
Usage Exception: None of the rules in your 'base' configuration matched a valid commit. Adjust rules or specify which commit you want to use explicitly.
$ arc which --show-base --base 'literal:derp'
derp
$ arc which --show-base --base 'arc:halt'
Usage Exception: None of the rules in your 'base' configuration matched a valid commit. Adjust rules or specify which commit you want to use explicitly.
$ arc set-config --local base git:origin/master
Set key 'base' = 'git:origin/master' in local config.
$ arc which --show-base
origin/master
$ arc which --show-base --base 'git:HEAD^'
HEAD^
$ arc which --show-base --base 'arc:yield, git:HEAD^'
origin/master
$ arc which --show-base --base 'arc:global, git:HEAD^'
HEAD^
$ arc which --show-base --base 'arc:global, git:merge-base(origin/master)'
3f4f8992fba8d1f142974da36a82bae900e247c0```
Reviewers: dschleimer, vrana
Reviewed By: dschleimer
CC: aran
Maniphest Tasks: T1233
Differential Revision: https://secure.phabricator.com/D2748
2012-06-15 14:01:28 -07:00
|
|
|
}
|
|
|
|
default:
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2011-01-09 15:22:25 -08:00
|
|
|
}
|