Commit 494fbdb0 authored by Tobias Hunger's avatar Tobias Hunger

Git: Do the right thing when commiting

Do the right thing when commiting in git. This allows
staged files to be commited without additional changes, etc.

Change-Id: Ib04c91cf9c105c4a2bbe013926112d6d5d3bade6
Reviewed-by: default avatarTobias Hunger <tobias.hunger@nokia.com>
parent 366a9d0d
......@@ -33,11 +33,10 @@
#include "commitdata.h"
#include <utils/qtcassert.h>
#include <QtCore/QCoreApplication>
#include <QtCore/QDebug>
#include <QtCore/QRegExp>
const char *const kBranchIndicatorC = "# On branch";
namespace Git {
namespace Internal {
......@@ -87,151 +86,121 @@ void CommitData::clear()
panelData.clear();
amendSHA1.clear();
stagedFiles.clear();
unstagedFiles.clear();
untrackedFiles.clear();
files.clear();
}
// Split a state/file spec from git status output
// '#<tab>modified:<blanks>git .pro'
// into state and file ('modified', 'git .pro').
CommitData::StateFilePair splitStateFileSpecification(const QString &line)
static CommitData::FileState stateFor(const QChar &c)
{
QPair<QString, QString> rc;
const int statePos = 2;
const int colonIndex = line.indexOf(QLatin1Char(':'), statePos);
if (colonIndex == -1)
return rc;
rc.first = line.mid(statePos, colonIndex - statePos);
int filePos = colonIndex + 1;
const QChar blank = QLatin1Char(' ');
while (line.at(filePos) == blank)
filePos++;
if (filePos < line.size())
rc.second = line.mid(filePos, line.size() - filePos);
return rc;
switch (c.unicode()) {
case ' ':
return CommitData::UntrackedFile;
case 'M':
return CommitData::ModifiedFile;
case 'A':
return CommitData::AddedFile;
case 'D':
return CommitData::DeletedFile;
case 'R':
return CommitData::RenamedFile;
case 'C':
return CommitData::CopiedFile;
case 'U':
return CommitData::UpdatedFile;
default:
return CommitData::UnknownFileState;
}
}
// Convenience to add a state/file spec to a list
static inline bool addStateFileSpecification(const QString &line, QList<CommitData::StateFilePair> *list)
static bool checkLine(const QString &stateInfo, const QString &file, QList<CommitData::StateFilePair> *files)
{
const CommitData::StateFilePair sf = splitStateFileSpecification(line);
if (sf.first.isEmpty() || sf.second.isEmpty())
Q_ASSERT(stateInfo.count() == 2);
Q_ASSERT(files);
if (stateInfo == "??") {
files->append(qMakePair(CommitData::UntrackedFile, file));
return true;
}
CommitData::FileState stagedState = stateFor(stateInfo.at(0));
if (stagedState == CommitData::UnknownFileState)
return false;
stagedState = static_cast<CommitData::FileState>(stagedState | CommitData::StagedFile);
if (stagedState != CommitData::StagedFile)
files->append(qMakePair(stagedState, file));
CommitData::FileState state = stateFor(stateInfo.at(1));
if (state == CommitData::UnknownFileState)
return false;
list->push_back(sf);
if (state != CommitData::UntrackedFile) {
QString newFile = file;
if (stagedState == CommitData::RenamedStagedFile || stagedState == CommitData::CopiedStagedFile)
newFile = file.mid(file.indexOf(QLatin1String(" -> ")) + 4);
files->append(qMakePair(state, newFile));
}
return true;
}
/* Parse a git status file list:
* \code
# Changes to be committed:
#<tab>modified:<blanks>git.pro
# Changed but not updated:
#<tab>modified:<blanks>git.pro
# Untracked files:
#<tab>git.pro
\endcode
*/
bool CommitData::filesEmpty() const
{
return stagedFiles.empty() && unstagedFiles.empty() && untrackedFiles.empty();
}
## branch_name
XY file
\endcode */
bool CommitData::parseFilesFromStatus(const QString &output)
{
enum State { None, CommitFiles, NotUpdatedFiles, UntrackedFiles };
const QStringList lines = output.split(QLatin1Char('\n'));
const QString branchIndicator = QLatin1String(kBranchIndicatorC);
const QString commitIndicator = QLatin1String("# Changes to be committed:");
const QString notUpdatedIndicator = QLatin1String("# Changed but not updated:");
const QString notUpdatedIndicatorGit174 = QLatin1String("# Changes not staged for commit:");
const QString untrackedIndicator = QLatin1String("# Untracked files:");
State s = None;
// Match added/changed-not-updated files: "#<tab>modified: foo.cpp"
QRegExp filesPattern(QLatin1String("#\\t[^:]+:\\s+.+"));
QTC_ASSERT(filesPattern.isValid(), return false);
const QStringList::const_iterator cend = lines.constEnd();
for (QStringList::const_iterator it = lines.constBegin(); it != cend; ++it) {
QString line = *it;
if (line.startsWith(branchIndicator)) {
panelInfo.branch = line.mid(branchIndicator.size() + 1);
continue;
}
if (line.startsWith(commitIndicator)) {
s = CommitFiles;
continue;
}
if (line.startsWith(notUpdatedIndicator) || line.startsWith(notUpdatedIndicatorGit174)) {
s = NotUpdatedFiles;
foreach (const QString &line, lines) {
if (line.isEmpty())
continue;
}
if (line.startsWith(untrackedIndicator)) {
// Now match untracked: "#<tab>foo.cpp"
s = UntrackedFiles;
filesPattern = QRegExp(QLatin1String("#\\t.+"));
QTC_ASSERT(filesPattern.isValid(), return false);
if (line.startsWith("## ")) {
// Branch indication:
panelInfo.branch = line.mid(3);
continue;
}
if (filesPattern.exactMatch(line)) {
switch (s) {
case CommitFiles:
addStateFileSpecification(line, &stagedFiles);
break;
case NotUpdatedFiles:
// skip submodules:
if (line.endsWith(QLatin1String(" (modified content)"))
|| line.endsWith(" (new commits)"))
line = line.left(line.lastIndexOf(QLatin1Char('(')) - 1);
addStateFileSpecification(line, &unstagedFiles);
break;
case UntrackedFiles:
untrackedFiles.push_back(line.mid(2).trimmed());
break;
case None:
break;
}
}
QTC_ASSERT(line.at(2) == ' ', continue);
if (!checkLine(line.mid(0, 2), line.mid(3), &files))
return false;
}
return true;
}
// Convert a spec pair list to a list of file names, optionally
// filter for a state
static QStringList specToFileNames(const QList<CommitData::StateFilePair> &files,
const QString &stateFilter)
{
typedef QList<CommitData::StateFilePair>::const_iterator ConstIterator;
if (files.empty())
return QStringList();
const bool emptyFilter = stateFilter.isEmpty();
QStringList rc;
const ConstIterator cend = files.constEnd();
for (ConstIterator it = files.constBegin(); it != cend; ++it)
if (emptyFilter || stateFilter == it->first)
rc.push_back(it->second);
return rc;
}
QStringList CommitData::stagedFileNames(const QString &stateFilter) const
{
return specToFileNames(stagedFiles, stateFilter);
return true;
}
QStringList CommitData::unstagedFileNames(const QString &stateFilter) const
QStringList CommitData::filterFiles(const CommitData::FileState &state) const
{
return specToFileNames(unstagedFiles, stateFilter);
QStringList result;
foreach (const StateFilePair &p, files) {
if (state == AllStates || state == p.first)
result.append(p.second);
}
return result;
}
QDebug operator<<(QDebug d, const CommitData &data)
QString CommitData::stateDisplayName(const FileState &state)
{
d << data.panelInfo << data.panelData;
d.nospace() << "Commit: " << data.stagedFiles << " Not updated: "
<< data.unstagedFiles << " Untracked: " << data.untrackedFiles;
return d;
QString resultState;
if (state == UntrackedFile)
return QCoreApplication::translate("Git::Internal::CommitData", "untracked");
if (state & StagedFile)
resultState = QCoreApplication::translate("Git::Internal::CommitData", "staged + ");
if (state & ModifiedFile)
resultState.append(QCoreApplication::translate("Git::Internal::CommitData", "modified"));
else if (state & AddedFile)
resultState.append(QCoreApplication::translate("Git::Internal::CommitData", "added"));
else if (state & DeletedFile)
resultState.append(QCoreApplication::translate("Git::Internal::CommitData", "deleted"));
else if (state & RenamedFile)
resultState.append(QCoreApplication::translate("Git::Internal::CommitData", "renamed"));
else if (state & CopiedFile)
resultState.append(QCoreApplication::translate("Git::Internal::CommitData", "copied"));
else if (state & UpdatedFile)
resultState.append(QCoreApplication::translate("Git::Internal::CommitData", "updated"));
return resultState;
}
} // namespace Internal
......
......@@ -69,34 +69,50 @@ QDebug operator<<(QDebug d, const GitSubmitEditorPanelData &);
class CommitData
{
public:
enum FileState {
UntrackedFile = 0,
StagedFile = 1,
ModifiedFile = 2,
AddedFile = 3,
DeletedFile = 4,
RenamedFile = 8,
CopiedFile = 16,
UpdatedFile = 32,
ModifiedStagedFile = StagedFile | ModifiedFile,
AddedStagedFile = StagedFile | AddedFile,
DeletedStagedFile = StagedFile | DeletedFile,
RenamedStagedFile = StagedFile | RenamedFile,
CopiedStagedFile = StagedFile | CopiedFile,
UpdatedStagedFile = StagedFile | UpdatedFile,
AllStates = UpdatedFile | CopiedFile | RenamedFile | DeletedFile | AddedFile | ModifiedFile | StagedFile,
UnknownFileState
};
// A pair of state string/file name ('modified', 'file.cpp').
typedef QPair<QString, QString> StateFilePair;
typedef QPair<FileState, QString> StateFilePair;
void clear();
// Parse the files and the branch of panelInfo
// from a git status output
bool parseFilesFromStatus(const QString &output);
bool filesEmpty() const;
// Convenience to retrieve the file names from
// the specification list. Optionally filter for a certain state
QStringList stagedFileNames(const QString &stateFilter = QString()) const;
QStringList unstagedFileNames(const QString &stateFilter = QString()) const;
QStringList filterFiles(const FileState &state = AllStates) const;
static QString stateDisplayName(const FileState &state);
QString amendSHA1;
QString commitEncoding;
GitSubmitEditorPanelInfo panelInfo;
GitSubmitEditorPanelData panelData;
QList<StateFilePair> stagedFiles;
QList<StateFilePair> unstagedFiles;
QStringList untrackedFiles;
QList<StateFilePair> files;
};
QDebug operator<<(QDebug d, const CommitData &);
} // namespace Internal
} // namespace Git
......
This diff is collapsed.
......@@ -57,6 +57,7 @@ namespace Core {
namespace VCSBase {
class VCSBaseEditorWidget;
class SubmitFileModel;
}
namespace Utils {
......@@ -204,16 +205,13 @@ public:
const GitSubmitEditorPanelData &data,
const QString &amendSHA1,
const QString &messageFile,
const QStringList &checkedFiles,
const QStringList &origCommitFiles,
const QStringList &origDeletedFiles);
VCSBase::SubmitFileModel *model);
enum StatusResult { StatusChanged, StatusUnchanged, StatusFailed };
StatusResult gitStatus(const QString &workingDirectory,
bool untracked = false,
QString *output = 0,
QString *errorMessage = 0,
bool *onBranch = 0);
QString *errorMessage = 0, bool *onBranch = 0);
void launchGitK(const QString &workingDirectory);
QStringList synchronousRepositoryBranches(const QString &repositoryURL);
......
......@@ -63,6 +63,7 @@
#include <utils/fileutils.h>
#include <vcsbase/basevcseditorfactory.h>
#include <vcsbase/submitfilemodel.h>
#include <vcsbase/vcsbaseeditor.h>
#include <vcsbase/basevcssubmiteditorfactory.h>
#include <vcsbase/vcsbaseoutputwindow.h>
......@@ -695,8 +696,6 @@ void GitPlugin::startCommit(bool amend)
// files to be able to unstage files the user unchecks
m_submitRepository = data.panelInfo.repository;
m_commitAmendSHA1 = data.amendSHA1;
m_submitOrigCommitFiles = data.stagedFileNames();
m_submitOrigDeleteFiles = data.stagedFileNames("deleted");
// Start new temp file with message template
Utils::TempFileSaver saver;
......@@ -772,21 +771,18 @@ bool GitPlugin::submitEditorAboutToClose(VCSBase::VCSBaseSubmitEditor *submitEdi
default:
break;
}
// Go ahead!
const QStringList fileList = editor->checkedFiles();
VCSBase::SubmitFileModel *model = qobject_cast<VCSBase::SubmitFileModel *>(editor->fileModel());
bool closeEditor = true;
if (!fileList.empty() || !m_commitAmendSHA1.isEmpty()) {
if (model->hasCheckedFiles() || !m_commitAmendSHA1.isEmpty()) {
// get message & commit
if (!m_core->fileManager()->saveFile(fileIFace))
return false;
closeEditor = m_gitClient->addAndCommit(m_submitRepository,
editor->panelData(),
m_commitAmendSHA1,
m_commitMessageFileName,
fileList,
m_submitOrigCommitFiles,
m_submitOrigDeleteFiles);
closeEditor = m_gitClient->addAndCommit(m_submitRepository, editor->panelData(),
m_commitAmendSHA1, m_commitMessageFileName, model);
}
if (closeEditor)
cleanCommitMessageFile();
......
......@@ -206,8 +206,6 @@ private:
QPointer<BranchDialog> m_branchDialog;
QPointer<RemoteDialog> m_remoteDialog;
QString m_submitRepository;
QStringList m_submitOrigCommitFiles;
QStringList m_submitOrigDeleteFiles;
QString m_commitMessageFileName;
QString m_commitAmendSHA1;
bool m_submitActionTriggered;
......
......@@ -44,9 +44,6 @@
namespace Git {
namespace Internal {
enum { FileTypeRole = Qt::UserRole + 1 };
enum FileType { StagedFile , UnstagedFile, UntrackedFile };
/* The problem with git is that no diff can be obtained to for a random
* multiselection of staged/unstaged files; it requires the --cached
* option for staged files. So, we sort apart the diff file lists
......@@ -64,21 +61,6 @@ GitSubmitEditorWidget *GitSubmitEditor::submitEditorWidget()
return static_cast<GitSubmitEditorWidget *>(widget());
}
// Utility to add a list of state/file pairs to the model
// setting a file type.
static void addStateFileListToModel(const QList<CommitData::StateFilePair> &l,
bool checked, FileType ft,
VCSBase::SubmitFileModel *model)
{
typedef QList<CommitData::StateFilePair>::const_iterator ConstIterator;
if (!l.empty()) {
const ConstIterator cend = l.constEnd();
const QVariant fileTypeData(ft);
for (ConstIterator it = l.constBegin(); it != cend; ++it)
model->addFile(it->second, it->first, checked).front()->setData(fileTypeData, FileTypeRole);
}
}
void GitSubmitEditor::setCommitData(const CommitData &d)
{
submitEditorWidget()->setPanelData(d.panelData);
......@@ -87,14 +69,14 @@ void GitSubmitEditor::setCommitData(const CommitData &d)
m_commitEncoding = d.commitEncoding;
m_model = new VCSBase::SubmitFileModel(this);
addStateFileListToModel(d.stagedFiles, true, StagedFile, m_model);
addStateFileListToModel(d.unstagedFiles, false, UnstagedFile, m_model);
if (!d.untrackedFiles.empty()) {
const QString untrackedSpec = QLatin1String("untracked");
const QVariant fileTypeData(UntrackedFile);
const QStringList::const_iterator cend = d.untrackedFiles.constEnd();
for (QStringList::const_iterator it = d.untrackedFiles.constBegin(); it != cend; ++it)
m_model->addFile(*it, untrackedSpec, false).front()->setData(fileTypeData, FileTypeRole);
if (!d.files.isEmpty()) {
for (QList<CommitData::StateFilePair>::const_iterator it = d.files.constBegin();
it != d.files.constEnd(); ++it) {
const CommitData::FileState state = it->first;
const QString file = it->second;
m_model->addFile(file, CommitData::stateDisplayName(state), state & CommitData::StagedFile,
QVariant(static_cast<int>(state)));
}
}
setFileModel(m_model);
}
......@@ -109,17 +91,11 @@ void GitSubmitEditor::slotDiffSelected(const QStringList &files)
for (int r = 0; r < rowCount; r++) {
const QString fileName = m_model->item(r, fileColumn)->text();
if (files.contains(fileName)) {
const FileType ft = static_cast<FileType>(m_model->item(r, 0)->data(FileTypeRole).toInt());
switch (ft) {
case StagedFile:
const CommitData::FileState state = static_cast<CommitData::FileState>(m_model->data(r).toInt());
if (state & CommitData::StagedFile)
stagedFiles.push_back(fileName);
break;
case UnstagedFile:
else if (state != CommitData::UntrackedFile)
unstagedFiles.push_back(fileName);
break;
case UntrackedFile:
break;
}
}
}
if (!unstagedFiles.empty() || !stagedFiles.empty())
......
......@@ -38,6 +38,29 @@
namespace VCSBase {
// --------------------------------------------------------------------------
// Helpers:
// --------------------------------------------------------------------------
static QList<QStandardItem *> createFileRow(const QString &fileName, const QString &status,
bool checked, const QVariant &v)
{
QStandardItem *statusItem = new QStandardItem(status);
statusItem->setCheckable(true);
statusItem->setCheckState(checked ? Qt::Checked : Qt::Unchecked);
statusItem->setFlags(Qt::ItemIsSelectable|Qt::ItemIsUserCheckable|Qt::ItemIsEnabled);
statusItem->setData(v);
QStandardItem *fileItem = new QStandardItem(fileName);
fileItem->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEnabled);
QList<QStandardItem *> row;
row << statusItem << fileItem;
return row;
}
// --------------------------------------------------------------------------
// SubmitFileModel:
// --------------------------------------------------------------------------
/*!
\class VCSBase::SubmitFileModel
......@@ -54,24 +77,10 @@ SubmitFileModel::SubmitFileModel(QObject *parent) :
setHorizontalHeaderLabels(headerLabels);
}
QList<QStandardItem *> SubmitFileModel::createFileRow(const QString &fileName, const QString &status, bool checked)
{
if (VCSBase::Constants::Internal::debug)
qDebug() << Q_FUNC_INFO << fileName << status << checked;
QStandardItem *statusItem = new QStandardItem(status);
statusItem->setCheckable(true);
statusItem->setCheckState(checked ? Qt::Checked : Qt::Unchecked);
statusItem->setFlags(Qt::ItemIsSelectable|Qt::ItemIsUserCheckable|Qt::ItemIsEnabled);
QStandardItem *fileItem = new QStandardItem(fileName);
fileItem->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEnabled);
QList<QStandardItem *> row;
row << statusItem << fileItem;
return row;
}
QList<QStandardItem *> SubmitFileModel::addFile(const QString &fileName, const QString &status, bool checked)
QList<QStandardItem *> SubmitFileModel::addFile(const QString &fileName, const QString &status, bool checked,
const QVariant &v)
{
const QList<QStandardItem *> row = createFileRow(fileName, status, checked);
const QList<QStandardItem *> row = createFileRow(fileName, status, checked, v);
appendRow(row);
return row;
}
......@@ -106,6 +115,13 @@ bool SubmitFileModel::checked(int row) const
return (item(row)->checkState() == Qt::Checked);
}
QVariant SubmitFileModel::data(int row) const
{
if (row < 0 || row >= rowCount())
return false;
return item(row)->data();
}
bool SubmitFileModel::hasCheckedFiles() const
{
for (int i = 0; i < rowCount(); ++i) {
......
......@@ -46,8 +46,8 @@ public:
explicit SubmitFileModel(QObject *parent = 0);
// Convenience to create and add rows containing a file plus status text.
static QList<QStandardItem *> createFileRow(const QString &fileName, const QString &status = QString(), bool checked = true);
QList<QStandardItem *> addFile(const QString &fileName, const QString &status = QString(), bool checked = true);
QList<QStandardItem *> addFile(const QString &fileName, const QString &status = QString(),
bool checked = true, const QVariant &data = QVariant());
// Find convenience that returns the whole row (as opposed to QStandardItemModel::find).
QList<QStandardItem *> findRow(const QString &text, int column = 0) const;
......@@ -58,6 +58,7 @@ public:
QString state(int row) const;
QString file(int row) const;
bool checked(int row) const;
QVariant data(int row) const;
bool hasCheckedFiles() const;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment