Git/DiffEditor: Fix staging added/removed lines separately

Fixes: QTCREATORBUG-23243
Change-Id: Ice19e1c778aabd9cb1b9fe0681234073de85cfcb
Reviewed-by: hjk <hjk@qt.io>
Reviewed-by: André Hartmann <aha_1980@gmx.de>
Reviewed-by: Orgad Shaneh <orgads@gmail.com>
This commit is contained in:
Jarek Kobus
2019-11-20 16:01:17 +01:00
parent 27d503558f
commit 3b9ce98865
7 changed files with 213 additions and 80 deletions

View File

@@ -76,6 +76,14 @@ DiffEditorController *DiffEditorDocument::controller() const
return m_controller; return m_controller;
} }
static void appendRow(ChunkData *chunk, const RowData &row)
{
const bool isSeparator = row.leftLine.textLineType == TextLineData::Separator
&& row.rightLine.textLineType == TextLineData::Separator;
if (!isSeparator)
chunk->rows.append(row);
}
ChunkData DiffEditorDocument::filterChunk(const ChunkData &data, ChunkData DiffEditorDocument::filterChunk(const ChunkData &data,
const ChunkSelection &selection, bool revert) const ChunkSelection &selection, bool revert)
{ {
@@ -83,27 +91,47 @@ ChunkData DiffEditorDocument::filterChunk(const ChunkData &data,
return data; return data;
ChunkData chunk(data); ChunkData chunk(data);
for (int i = 0; i < chunk.rows.count(); ++i) { chunk.rows.clear();
RowData &row = chunk.rows[i]; for (int i = 0; i < data.rows.count(); ++i) {
if (i < selection.startRow || i >= selection.startRow + selection.selectedRowsCount) { RowData row = data.rows[i];
const bool isLeftSelected = selection.leftSelection.contains(i);
const bool isRightSelected = selection.rightSelection.contains(i);
if (isLeftSelected || isRightSelected) {
if (row.equal || (isLeftSelected && isRightSelected)) {
appendRow(&chunk, row);
} else if (isLeftSelected) {
RowData newRow = row;
row.rightLine = TextLineData(TextLineData::Separator);
appendRow(&chunk, row);
if (revert) {
newRow.leftLine = newRow.rightLine;
newRow.equal = true;
appendRow(&chunk, newRow);
}
} else { // isRightSelected
if (!revert) {
RowData newRow = row;
newRow.rightLine = newRow.leftLine;
newRow.equal = true;
appendRow(&chunk, newRow);
}
row.leftLine = TextLineData(TextLineData::Separator);
appendRow(&chunk, row);
}
} else {
if (revert) if (revert)
row.leftLine = row.rightLine; row.leftLine = row.rightLine;
else else
row.rightLine = row.leftLine; row.rightLine = row.leftLine;
row.equal = true; row.equal = true;
appendRow(&chunk, row);
} }
} }
for (int i = 0; i < chunk.rows.count(); ) {
const RowData &row = chunk.rows[i];
const bool isSeparator = row.leftLine.textLineType == TextLineData::Separator
&& row.rightLine.textLineType == TextLineData::Separator;
if (isSeparator)
chunk.rows.removeAt(i);
else
++i;
}
return chunk; return chunk;
} }

View File

@@ -1431,11 +1431,14 @@ void DiffEditor::Internal::DiffEditorPlugin::testReadPatch()
} }
} }
using ListOfStringPairs = QList<QPair<QString, QString>>;
void DiffEditor::Internal::DiffEditorPlugin::testFilterPatch_data() void DiffEditor::Internal::DiffEditorPlugin::testFilterPatch_data()
{ {
QTest::addColumn<ChunkData>("chunk"); QTest::addColumn<ChunkData>("chunk");
QTest::addColumn<QStringList>("rightLines"); QTest::addColumn<ListOfStringPairs>("rows");
QTest::addColumn<ChunkSelection>("selection"); QTest::addColumn<ChunkSelection>("selection");
QTest::addColumn<bool>("revert");
auto createChunk = []() { auto createChunk = []() {
ChunkData chunk; ChunkData chunk;
@@ -1455,29 +1458,29 @@ void DiffEditor::Internal::DiffEditorPlugin::testFilterPatch_data()
chunk->rows.append(row); chunk->rows.append(row);
}; };
ChunkData chunk; ChunkData chunk;
QStringList rightLines; ListOfStringPairs rows;
chunk = createChunk(); chunk = createChunk();
appendRow(&chunk, "A", "A"); // 50 appendRow(&chunk, "A", "A"); // 50
appendRow(&chunk, "", "B"); // 51 + appendRow(&chunk, "", "B"); // 51 +
appendRow(&chunk, "C", "C"); // 52 appendRow(&chunk, "C", "C"); // 52
rightLines = QStringList { rows = ListOfStringPairs {
"A", {"A", "A"},
"B", {"", "B"},
"C" {"C", "C"}
}; };
QTest::newRow("one added") << chunk << rightLines << ChunkSelection(); QTest::newRow("one added") << chunk << rows << ChunkSelection() << false;
chunk = createChunk(); chunk = createChunk();
appendRow(&chunk, "A", "A"); // 50 appendRow(&chunk, "A", "A"); // 50
appendRow(&chunk, "B", ""); // 51 - appendRow(&chunk, "B", ""); // 51 -
appendRow(&chunk, "C", "C"); // 52 appendRow(&chunk, "C", "C"); // 52
rightLines = QStringList { rows = ListOfStringPairs {
"A", {"A", "A"},
"", {"B", ""},
"C" {"C", "C"}
}; };
QTest::newRow("one removed") << chunk << rightLines << ChunkSelection(); QTest::newRow("one removed") << chunk << rows << ChunkSelection() << false;
chunk = createChunk(); chunk = createChunk();
appendRow(&chunk, "A", "A"); // 50 appendRow(&chunk, "A", "A"); // 50
@@ -1486,26 +1489,26 @@ void DiffEditor::Internal::DiffEditorPlugin::testFilterPatch_data()
appendRow(&chunk, "", "D"); // 53 + appendRow(&chunk, "", "D"); // 53 +
appendRow(&chunk, "", "E"); // 54 appendRow(&chunk, "", "E"); // 54
appendRow(&chunk, "F", "F"); // 55 appendRow(&chunk, "F", "F"); // 55
rightLines = QStringList { rows = ListOfStringPairs {
"A", {"A", "A"},
"C", {"", "C"},
"D", {"", "D"},
"F", {"F", "F"}
}; };
QTest::newRow("stage selected added") << chunk << rightLines << ChunkSelection(2, 2); QTest::newRow("stage selected added") << chunk << rows << ChunkSelection({2, 3}, {2, 3}) << false;
chunk = createChunk(); chunk = createChunk();
appendRow(&chunk, "A", "A"); // 50 appendRow(&chunk, "A", "A"); // 50
appendRow(&chunk, "", "B"); // 51 + appendRow(&chunk, "", "B"); // 51 +
appendRow(&chunk, "C", "D"); // 52 appendRow(&chunk, "C", "D"); // 52
appendRow(&chunk, "E", "E"); // 53 appendRow(&chunk, "E", "E"); // 53
rightLines = QStringList { rows = ListOfStringPairs {
"A", {"A", "A"},
"B", {"", "B"},
"C", {"C", "C"},
"E", {"E", "E"}
}; };
QTest::newRow("stage selected added keep changed") << chunk << rightLines << ChunkSelection(1, 1); QTest::newRow("stage selected added keep changed") << chunk << rows << ChunkSelection({1}, {1}) << false;
chunk = createChunk(); chunk = createChunk();
appendRow(&chunk, "A", "A"); // 50 appendRow(&chunk, "A", "A"); // 50
@@ -1514,15 +1517,15 @@ void DiffEditor::Internal::DiffEditorPlugin::testFilterPatch_data()
appendRow(&chunk, "D", ""); // 53 - appendRow(&chunk, "D", ""); // 53 -
appendRow(&chunk, "E", ""); // 54 appendRow(&chunk, "E", ""); // 54
appendRow(&chunk, "F", "F"); // 55 appendRow(&chunk, "F", "F"); // 55
rightLines = QStringList { rows = ListOfStringPairs {
"A", {"A", "A"},
"B", {"B", "B"},
"", {"C", ""},
"", {"D", ""},
"E", {"E", "E"},
"F", {"F", "F"}
}; };
QTest::newRow("stage selected removed") << chunk << rightLines << ChunkSelection(2, 2); QTest::newRow("stage selected removed") << chunk << rows << ChunkSelection({2, 3}, {2, 3}) << false;
chunk = createChunk(); chunk = createChunk();
appendRow(&chunk, "A", "A"); // 50 appendRow(&chunk, "A", "A"); // 50
@@ -1531,37 +1534,132 @@ void DiffEditor::Internal::DiffEditorPlugin::testFilterPatch_data()
appendRow(&chunk, "", "D"); // 53 + appendRow(&chunk, "", "D"); // 53 +
appendRow(&chunk, "", "E"); // 54 appendRow(&chunk, "", "E"); // 54
appendRow(&chunk, "F", "F"); // 55 appendRow(&chunk, "F", "F"); // 55
rightLines = QStringList { rows = ListOfStringPairs {
"A", {"A", "A"},
"B", {"B", "B"},
"", {"C", ""},
"D", {"", "D"},
"F", {"F", "F"}
}; };
QTest::newRow("stage selected added/removed") << chunk << rightLines << ChunkSelection(2, 2); QTest::newRow("stage selected added/removed") << chunk << rows << ChunkSelection({2, 3}, {2, 3}) << false;
chunk = createChunk(); chunk = createChunk();
appendRow(&chunk, "A", "A"); // 50 appendRow(&chunk, "A", "A"); // 50
appendRow(&chunk, "B", "C"); // 51 -/+ appendRow(&chunk, "B", "C"); // 51 -/+
appendRow(&chunk, "D", "D"); // 52 appendRow(&chunk, "D", "D"); // 52
rightLines = QStringList { rows = ListOfStringPairs {
"A", {"A", "A"},
"C", {"B", "C"},
"D", {"D", "D"}
}; };
QTest::newRow("stage modified row") << chunk << rightLines << ChunkSelection(1, 1); QTest::newRow("stage modified row") << chunk << rows << ChunkSelection({1}, {1}) << false;
chunk = createChunk();
appendRow(&chunk, "A", "A"); // 50
appendRow(&chunk, "B", "C"); // 51 -/+
appendRow(&chunk, "D", "D"); // 52
rows = ListOfStringPairs {
{"A", "A"},
{"B", "C"},
{"D", "D"}
};
QTest::newRow("stage modified and unmodified rows") << chunk << rows << ChunkSelection({0, 1, 2}, {0, 1, 2}) << false;
chunk = createChunk();
appendRow(&chunk, "A", "A"); // 50
appendRow(&chunk, "B", "C"); // 51 -/+
appendRow(&chunk, "D", "D"); // 52
rows = ListOfStringPairs {
{"A", "A"},
{"B", "C"},
{"D", "D"}
};
QTest::newRow("stage unmodified left rows") << chunk << rows << ChunkSelection({0, 1, 2}, {1}) << false;
chunk = createChunk();
appendRow(&chunk, "A", "A"); // 50
appendRow(&chunk, "B", "C"); // 51 -/+
appendRow(&chunk, "D", "D"); // 52
rows = ListOfStringPairs {
{"A", "A"},
{"B", "C"},
{"D", "D"}
};
QTest::newRow("stage unmodified right rows") << chunk << rows << ChunkSelection({1}, {0, 1, 2}) << false;
chunk = createChunk();
appendRow(&chunk, "A", "A"); // 50
appendRow(&chunk, "B", "C"); // 51 -/+
appendRow(&chunk, "D", "D"); // 52
rows = ListOfStringPairs {
{"A", "A"},
{"B", ""},
{"D", "D"}
};
QTest::newRow("stage left only") << chunk << rows << ChunkSelection({1}, {}) << false;
chunk = createChunk();
appendRow(&chunk, "A", "A"); // 50
appendRow(&chunk, "B", "C"); // 51 -/+
appendRow(&chunk, "D", "D"); // 52
rows = ListOfStringPairs {
{"A", "A"},
{"B", "B"},
{"", "C"},
{"D", "D"}
};
QTest::newRow("stage right only") << chunk << rows << ChunkSelection({}, {1}) << false;
chunk = createChunk();
appendRow(&chunk, "A", "A"); // 50
appendRow(&chunk, "B", "C"); // 51 -/+
appendRow(&chunk, "D", "D"); // 52
rows = ListOfStringPairs {
{"A", "A"},
{"B", "C"},
{"D", "D"}
};
QTest::newRow("stage modified row and revert") << chunk << rows << ChunkSelection({1}, {1}) << true;
chunk = createChunk();
appendRow(&chunk, "A", "A"); // 50
appendRow(&chunk, "B", "C"); // 51 -/+
appendRow(&chunk, "D", "D"); // 52
rows = ListOfStringPairs {
{"A", "A"},
{"B", ""},
{"C", "C"},
{"D", "D"}
};
// symmetric to: "stage right only"
QTest::newRow("stage left only and revert") << chunk << rows << ChunkSelection({1}, {}) << true;
chunk = createChunk();
appendRow(&chunk, "A", "A"); // 50
appendRow(&chunk, "B", "C"); // 51 -/+
appendRow(&chunk, "D", "D"); // 52
rows = ListOfStringPairs {
{"A", "A"},
{"", "C"},
{"D", "D"}
};
// symmetric to: "stage left only"
QTest::newRow("stage right only and revert") << chunk << rows << ChunkSelection({}, {1}) << true;
} }
void DiffEditor::Internal::DiffEditorPlugin::testFilterPatch() void DiffEditor::Internal::DiffEditorPlugin::testFilterPatch()
{ {
QFETCH(ChunkData, chunk); QFETCH(ChunkData, chunk);
QFETCH(QStringList, rightLines); QFETCH(ListOfStringPairs, rows);
QFETCH(ChunkSelection, selection); QFETCH(ChunkSelection, selection);
QFETCH(bool, revert);
ChunkData result = DiffEditorDocument::filterChunk(chunk, selection, false); const ChunkData result = DiffEditorDocument::filterChunk(chunk, selection, revert);
QCOMPARE(result.rows.size(), rightLines.size()); QCOMPARE(result.rows.size(), rows.size());
for (int i = 0; i < rightLines.size(); ++i) { for (int i = 0; i < rows.size(); ++i) {
QCOMPARE(result.rows.at(i).rightLine.text, rightLines.at(i)); QCOMPARE(result.rows.at(i).leftLine.text, rows.at(i).first);
QCOMPARE(result.rows.at(i).rightLine.text, rows.at(i).second);
} }
} }

View File

@@ -26,6 +26,7 @@
#include "diffutils.h" #include "diffutils.h"
#include <texteditor/fontsettings.h> #include <texteditor/fontsettings.h>
#include <utils/algorithm.h>
#include <utils/differ.h> #include <utils/differ.h>
#include <QFutureInterfaceBase> #include <QFutureInterfaceBase>
@@ -37,6 +38,11 @@ using namespace Utils;
namespace DiffEditor { namespace DiffEditor {
int ChunkSelection::selectedRowsCount() const
{
return Utils::toSet(leftSelection).unite(Utils::toSet(rightSelection)).count();
}
static QList<TextLineData> assemblyRows(const QList<TextLineData> &lines, static QList<TextLineData> assemblyRows(const QList<TextLineData> &lines,
const QMap<int, int> &lineSpans) const QMap<int, int> &lineSpans)
{ {

View File

@@ -102,10 +102,12 @@ public:
class DIFFEDITOR_EXPORT ChunkSelection { class DIFFEDITOR_EXPORT ChunkSelection {
public: public:
ChunkSelection() {} ChunkSelection() {}
ChunkSelection(int s, int c) : startRow(s), selectedRowsCount(c) {} ChunkSelection(const QList<int> &left, const QList<int> &right)
bool isNull() const { return selectedRowsCount <= 0; } : leftSelection(left), rightSelection(right) {}
int startRow = -1; bool isNull() const { return leftSelection.isEmpty() && rightSelection.isEmpty(); }
int selectedRowsCount = 0; int selectedRowsCount() const;
QList<int> leftSelection;
QList<int> rightSelection;
}; };
class DIFFEDITOR_EXPORT FileData { class DIFFEDITOR_EXPORT FileData {

View File

@@ -512,7 +512,11 @@ void SideDiffEditorWidget::contextMenuEvent(QContextMenuEvent *e)
? chunkRowForBlockNumber(endBlockNumber) ? chunkRowForBlockNumber(endBlockNumber)
: chunkRowsCountForBlockNumber(blockNumber); : chunkRowsCountForBlockNumber(blockNumber);
const ChunkSelection selection(selectionStart, selectionEnd - selectionStart + 1); QList<int> rows;
for (int i = selectionStart; i <= selectionEnd; ++i)
rows.append(i);
const ChunkSelection selection(rows, rows);
emit contextMenuRequested(menu, fileIndexForBlockNumber(blockNumber), emit contextMenuRequested(menu, fileIndexForBlockNumber(blockNumber),
chunkIndexForBlockNumber(blockNumber), chunkIndexForBlockNumber(blockNumber),

View File

@@ -194,8 +194,7 @@ void UnifiedDiffEditorWidget::contextMenuEvent(QContextMenuEvent *e)
const ChunkData chunkData = m_controller.chunkData(fileIndex, chunkIndex); const ChunkData chunkData = m_controller.chunkData(fileIndex, chunkIndex);
int selectionStart = -1; QList<int> leftSelection, rightSelection;
int selectionEnd = -1;
for (int i = startBlockNumber; i <= endBlockNumber; ++i) { for (int i = startBlockNumber; i <= endBlockNumber; ++i) {
const int currentFileIndex = fileIndexForBlockNumber(i); const int currentFileIndex = fileIndexForBlockNumber(i);
@@ -214,18 +213,14 @@ void UnifiedDiffEditorWidget::contextMenuEvent(QContextMenuEvent *e)
const int leftRow = m_leftLineNumbers.value(i, qMakePair(-1, -1)).second; const int leftRow = m_leftLineNumbers.value(i, qMakePair(-1, -1)).second;
const int rightRow = m_rightLineNumbers.value(i, qMakePair(-1, -1)).second; const int rightRow = m_rightLineNumbers.value(i, qMakePair(-1, -1)).second;
const int row = leftRow >= 0 ? leftRow : rightRow;
if (row < 0) if (leftRow >= 0)
continue; leftSelection.append(leftRow);
if (rightRow >= 0)
if (selectionStart < 0 || selectionStart > row) rightSelection.append(rightRow);
selectionStart = row;
if (selectionEnd < 0 || selectionEnd < row)
selectionEnd = row;
} }
const ChunkSelection selection(selectionStart, selectionEnd - selectionStart + 1); const ChunkSelection selection(leftSelection, rightSelection);
addContextMenuActions(menu, fileIndexForBlockNumber(blockNumber), addContextMenuActions(menu, fileIndexForBlockNumber(blockNumber),
chunkIndexForBlockNumber(blockNumber), selection); chunkIndexForBlockNumber(blockNumber), selection);

View File

@@ -875,7 +875,7 @@ void GitClient::chunkActionsRequested(QMenu *menu, int fileIndex, int chunkIndex
stageChunk(diffController, fileIndex, chunkIndex, stageChunk(diffController, fileIndex, chunkIndex,
DiffEditorController::NoOption, DiffEditor::ChunkSelection()); DiffEditorController::NoOption, DiffEditor::ChunkSelection());
}); });
QAction *stageLinesAction = menu->addAction(tr("Stage %n Line(s)", "", selection.selectedRowsCount)); QAction *stageLinesAction = menu->addAction(tr("Stage Selection (%n Lines)", "", selection.selectedRowsCount()));
connect(stageLinesAction, &QAction::triggered, this, connect(stageLinesAction, &QAction::triggered, this,
[stageChunk, diffController, fileIndex, chunkIndex, selection]() { [stageChunk, diffController, fileIndex, chunkIndex, selection]() {
stageChunk(diffController, fileIndex, chunkIndex, stageChunk(diffController, fileIndex, chunkIndex,
@@ -887,7 +887,7 @@ void GitClient::chunkActionsRequested(QMenu *menu, int fileIndex, int chunkIndex
stageChunk(diffController, fileIndex, chunkIndex, stageChunk(diffController, fileIndex, chunkIndex,
DiffEditorController::Revert, DiffEditor::ChunkSelection()); DiffEditorController::Revert, DiffEditor::ChunkSelection());
}); });
QAction *unstageLinesAction = menu->addAction(tr("Unstage %n Line(s)", "", selection.selectedRowsCount)); QAction *unstageLinesAction = menu->addAction(tr("Unstage Selection (%n Lines)", "", selection.selectedRowsCount()));
connect(unstageLinesAction, &QAction::triggered, this, connect(unstageLinesAction, &QAction::triggered, this,
[stageChunk, diffController, fileIndex, chunkIndex, selection]() { [stageChunk, diffController, fileIndex, chunkIndex, selection]() {
stageChunk(diffController, fileIndex, chunkIndex, stageChunk(diffController, fileIndex, chunkIndex,