Page MenuHomePhabricator

D14137.id34159.diff
No OneTemporary

D14137.id34159.diff

diff --git a/src/internationalization/ArcanistUSEnglishTranslation.php b/src/internationalization/ArcanistUSEnglishTranslation.php
--- a/src/internationalization/ArcanistUSEnglishTranslation.php
+++ b/src/internationalization/ArcanistUSEnglishTranslation.php
@@ -68,6 +68,16 @@
'Ignore this untracked file and continue?',
'Ignore these untracked files and continue?',
),
+
+ '%s submodule(s) have uncommitted or untracked changes:' => array(
+ 'A submodule has uncommitted or untracked changes:',
+ 'Submodules have uncommitted or untracked changes:',
+ ),
+
+ 'Ignore the changes to these %s submodule(s) and continue?' => array(
+ 'Ignore the change to this submodule and continue?',
+ 'Ignore the changes to these submodules and continue?',
+ ),
);
}
diff --git a/src/repository/api/ArcanistGitAPI.php b/src/repository/api/ArcanistGitAPI.php
--- a/src/repository/api/ArcanistGitAPI.php
+++ b/src/repository/api/ArcanistGitAPI.php
@@ -672,7 +672,7 @@
$result = new PhutilArrayWithDefaultValue();
list($stdout) = $uncommitted_future->resolvex();
- $uncommitted_files = $this->parseGitStatus($stdout);
+ $uncommitted_files = $this->parseGitRawDiff($stdout);
foreach ($uncommitted_files as $path => $mask) {
$result[$path] |= ($mask | self::FLAG_UNCOMMITTED);
}
@@ -704,7 +704,7 @@
$this->getDiffBaseOptions(),
$this->getBaseCommit());
- return $this->parseGitStatus($stdout);
+ return $this->parseGitRawDiff($stdout);
}
public function getGitConfig($key, $default = null) {
@@ -759,7 +759,7 @@
return $this;
}
- private function parseGitStatus($status, $full = false) {
+ private function parseGitRawDiff($status, $full = false) {
static $flags = array(
'A' => self::FLAG_ADDED,
'M' => self::FLAG_MODIFIED,
@@ -777,17 +777,51 @@
$files = array();
foreach ($lines as $line) {
$mask = 0;
+
+ // "git diff --raw" lines begin with a ":" character.
+ $old_mode = ltrim($line[0], ':');
+ $new_mode = $line[1];
+
+ // The hashes may be padded with "." characters for alignment. Discard
+ // them.
+ $old_hash = rtrim($line[2], '.');
+ $new_hash = rtrim($line[3], '.');
+
$flag = $line[4];
$file = $line[5];
- foreach ($flags as $key => $bits) {
- if ($flag == $key) {
- $mask |= $bits;
- }
+
+ $new_value = intval($new_mode, 8);
+ $is_submodule = (($new_value & 0160000) === 0160000);
+
+ if (($is_submodule) &&
+ ($flag == 'M') &&
+ ($old_hash === $new_hash) &&
+ ($old_mode === $new_mode)) {
+ // See T9455. We see this submodule as "modified", but the old and new
+ // hashes are the same and the old and new modes are the same, so we
+ // don't directly see a modification.
+
+ // We can end up here if we have a submodule which has uncommitted
+ // changes inside of it (for example, the user has added untracked
+ // files or made uncommitted changes to files in the submodule). In
+ // this case, we set a different flag because we can't meaningfully
+ // give users the same prompt.
+
+ // Note that if the submodule has real changes from the parent
+ // perspective (the base commit has changed) and also has uncommitted
+ // changes, we'll only see the real changes and miss the uncommitted
+ // changes. At the time of writing, there is no reasonable porcelain
+ // for finding those changes, and the impact of this error seems small.
+
+ $mask |= self::FLAG_EXTERNALS;
+ } else if (isset($flags[$flag])) {
+ $mask |= $flags[$flag];
}
+
if ($full) {
$files[$file] = array(
'mask' => $mask,
- 'ref' => rtrim($line[3], '.'),
+ 'ref' => $new_hash,
);
} else {
$files[$file] = $mask;
@@ -807,7 +841,7 @@
list($stdout) = $this->execxLocal(
'diff --raw %s',
$since_commit);
- return $this->parseGitStatus($stdout);
+ return $this->parseGitRawDiff($stdout);
}
public function getBlame($path) {
diff --git a/src/repository/api/ArcanistRepositoryAPI.php b/src/repository/api/ArcanistRepositoryAPI.php
--- a/src/repository/api/ArcanistRepositoryAPI.php
+++ b/src/repository/api/ArcanistRepositoryAPI.php
@@ -15,6 +15,9 @@
const FLAG_MISSING = 32;
const FLAG_UNSTAGED = 64;
const FLAG_UNCOMMITTED = 128;
+
+ // Occurs in SVN when you have uncommitted changes to a modified external,
+ // or in Git when you have uncommitted or untracked changes in a submodule.
const FLAG_EXTERNALS = 256;
// Occurs in SVN when you replace a file with a directory without telling
@@ -195,6 +198,14 @@
/**
* @task status
*/
+ final public function getDirtyExternalChanges() {
+ return $this->getUncommittedPathsWithMask(self::FLAG_EXTERNALS);
+ }
+
+
+ /**
+ * @task status
+ */
private function getUncommittedPathsWithMask($mask) {
$match = array();
foreach ($this->getUncommittedStatus() as $path => $flags) {
diff --git a/src/workflow/ArcanistWorkflow.php b/src/workflow/ArcanistWorkflow.php
--- a/src/workflow/ArcanistWorkflow.php
+++ b/src/workflow/ArcanistWorkflow.php
@@ -893,11 +893,47 @@
implode("\n ", $missing)));
}
+ $externals = $api->getDirtyExternalChanges();
+
+ // TODO: This state can exist in Subversion, but it is currently handled
+ // elsewhere. It should probably be handled here, eventually.
+ if ($api instanceof ArcanistSubversionAPI) {
+ $externals = array();
+ }
+
+ if ($externals) {
+ $message = pht(
+ '%s submodule(s) have uncommitted or untracked changes:',
+ new PhutilNumber(count($externals)));
+
+ $prompt = pht(
+ 'Ignore the changes to these %s submodule(s) and continue?',
+ new PhutilNumber(count($externals)));
+
+ $list = id(new PhutilConsoleList())
+ ->setWrap(false)
+ ->addItems($externals);
+
+ id(new PhutilConsoleBlock())
+ ->addParagraph($message)
+ ->addList($list)
+ ->draw();
+
+ $ok = phutil_console_confirm($prompt, $default_no = false);
+ if (!$ok) {
+ throw new ArcanistUserAbortException();
+ }
+ }
+
$uncommitted = $api->getUncommittedChanges();
$unstaged = $api->getUnstagedChanges();
+ // We already dealt with externals.
+ $unstaged = array_diff($unstaged, $externals);
+
// We only want files which are purely uncommitted.
$uncommitted = array_diff($uncommitted, $unstaged);
+ $uncommitted = array_diff($uncommitted, $externals);
$untracked = $api->getUntrackedChanges();
if (!$this->shouldRequireCleanUntrackedFiles()) {

File Metadata

Mime Type
text/plain
Expires
Fri, Mar 21, 10:34 AM (2 w, 5 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7715358
Default Alt Text
D14137.id34159.diff (6 KB)

Event Timeline