Page Menu
Home
Phabricator (Chris)
Search
Configure Global Search
Log In
Files
F103447
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
170 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/Git.cpp b/src/Git.cpp
index 4c5ea6a..9bec4f6 100644
--- a/src/Git.cpp
+++ b/src/Git.cpp
@@ -1,1625 +1,1628 @@
#include "Git.h"
#include "GitObjectManager.h"
#include "MyProcess.h"
#include "common/joinpath.h"
#include "common/misc.h"
#include <QDateTime>
#include <QDebug>
#include <QDir>
#include <QDirIterator>
#include <QProcess>
#include <QThread>
#include <QTimer>
#include <set>
#define DEBUGLOG 0
using callback_t = Git::callback_t;
struct Git::Private {
struct Info {
QString git_command;
QString working_repo_dir;
QString submodule_path;
callback_t fn_log_writer_callback = nullptr;
void *callback_cookie = nullptr;
};
Info info;
QString ssh_command;// = "C:/Program Files/Git/usr/bin/ssh.exe";
QString ssh_key_override;// = "C:/a/id_rsa";
std::vector<char> result;
QString error_message;
int process_exit_code = 0;
};
Git::Git()
: m(new Private)
{
}
Git::Git(const Context &cx, QString const &repodir, const QString &submodpath, const QString &sshkey)
: m(new Private)
{
setGitCommand(cx.git_command, cx.ssh_command);
setWorkingRepositoryDir(repodir, submodpath, sshkey);
}
Git::~Git()
{
delete m;
}
void Git::setLogCallback(callback_t func, void *cookie)
{
m->info.fn_log_writer_callback = func;
m->info.callback_cookie = cookie;
}
void Git::setWorkingRepositoryDir(QString const &repo, const QString &submodpath, QString const &sshkey)
{
m->info.working_repo_dir = repo;
m->info.submodule_path = submodpath;
m->ssh_key_override = sshkey;
}
QString Git::workingDir() const
{
QString dir = m->info.working_repo_dir;
if (!m->info.submodule_path.isEmpty()) {
dir = dir / m->info.submodule_path;
}
return dir;
}
QString const &Git::sshKey() const
{
return m->ssh_key_override;
}
void Git::setSshKey(QString const &sshkey) const
{
m->ssh_key_override = sshkey;
}
bool Git::isValidID(QString const &id)
{
int zero = 0;
int n = id.size();
if (n >= 4 && n <= GIT_ID_LENGTH) {
ushort const *p = id.utf16();
for (int i = 0; i < n; i++) {
uchar c = p[i];
if (c == '0') {
zero++;
} else if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')) {
// ok
} else {
return false;
}
}
if (zero == GIT_ID_LENGTH) { // 全部 0 の時は false を返す
return false;
}
return true; // ok
}
return false;
}
QString Git::status()
{
git("status");
return resultText();
}
QByteArray Git::toQByteArray() const
{
if (m->result.empty()) return QByteArray();
return QByteArray(&m->result[0], m->result.size());
}
QString Git::resultText() const
{
return QString::fromUtf8(toQByteArray());
}
void Git::setGitCommand(QString const &gitcmd, QString const &sshcmd)
{
m->info.git_command = gitcmd;
m->ssh_command = sshcmd;
}
QString Git::gitCommand() const
{
Q_ASSERT(m);
return m->info.git_command;
}
void Git::clearResult()
{
m->result.clear();
m->process_exit_code = 0;
m->error_message = QString();
}
QString Git::errorMessage() const
{
return m->error_message;
}
int Git::getProcessExitCode() const
{
return m->process_exit_code;
}
bool Git::chdirexec(std::function<bool()> const &fn)
{
bool ok = false;
QString cwd = QDir::currentPath();
QString dir = workingDir();
if (QDir::setCurrent(dir)) {
ok = fn();
QDir::setCurrent(cwd);
}
return ok;
}
bool Git::git(QString const &arg, bool chdir, bool errout, AbstractPtyProcess *pty)
{
QFileInfo info(gitCommand());
if (!info.isExecutable()) {
qDebug() << "Invalid git command: " << gitCommand();
return false;
}
#if DEBUGLOG
qDebug() << "exec: git " << arg;
QTime timer;
timer.start();
#endif
clearResult();
QString env;
if (m->ssh_command.isEmpty() || m->ssh_key_override.isEmpty()) {
// nop
} else {
if (m->ssh_command.indexOf('\"') >= 0) return false;
if (m->ssh_key_override.indexOf('\"') >= 0) return false;
if (!QFileInfo(m->ssh_command).isExecutable()) return false;
env = QString("GIT_SSH_COMMAND=\"%1\" -i \"%2\" ").arg(m->ssh_command).arg(m->ssh_key_override);
}
auto DoIt = [&](){
QString cmd = QString("\"%1\" --no-pager ").arg(gitCommand());
cmd += arg;
if (m->info.fn_log_writer_callback) {
QByteArray ba;
ba.append("> git ");
ba.append(arg);
ba.append('\n');
m->info.fn_log_writer_callback(m->info.callback_cookie, ba.data(), (int)ba.size());
}
if (pty) {
pty->start(cmd, env, pty->userVariant());
m->process_exit_code = 0; // バックグラウンドで実行を継続するけど、とりあえず成功したことにしておく
} else {
Process proc;
proc.start(cmd, false);
m->process_exit_code = proc.wait();
if (errout) {
m->result = proc.errbytes;
} else {
m->result = proc.outbytes;
}
m->error_message = proc.errstring();
}
return m->process_exit_code == 0;
};
bool ok = false;
if (chdir) {
if (pty) {
pty->setChangeDir(workingDir());
ok = DoIt();
} else {
ok = chdirexec(DoIt);
}
} else {
ok = DoIt();
}
#if DEBUGLOG
qDebug() << timer.elapsed() << "ms";
#endif
return ok;
}
GitPtr Git::dup() const
{
Git *p = new Git();
p->m->info = m->info;
return GitPtr(p);
}
bool Git::isValidWorkingCopy(QString const &dir)
{
return QFileInfo(dir).isDir() && QDir(dir / ".git").exists();
}
bool Git::isValidWorkingCopy() const
{
return isValidWorkingCopy(workingDir());
}
QString Git::version()
{
git("--version", false);
return resultText().trimmed();
}
bool Git::init()
{
bool ok = false;
QDir cwd = QDir::current();
QString dir = workingDir();
if (QDir::setCurrent(dir)) {
QString gitdir = dir / ".git";
if (!QFileInfo(gitdir).isDir()) {
git("init", false);
if (QFileInfo(gitdir).isDir()) {
ok = true;
}
}
QDir::setCurrent(cwd.path());
}
return ok;
}
QString Git::rev_parse(QString const &name)
{
QString cmd = "rev-parse %1";
cmd = cmd.arg(name);
if (git(cmd)) {
return resultText().trimmed();
}
return QString();
}
QList<Git::Tag> Git::tags()
{
#if 0
auto MidCmp = [](QString const &line, int i, char const *ptr){
ushort const *p = line.utf16();
for (int j = 0; ptr[j]; j++) {
if (p[i + j] != ptr[j]) return false;
}
return true;
};
QList<Git::Tag> list;
QStringList lines = refs();
for (QString const &line : lines) {
Tag tag;
if (line.isEmpty()) continue;
int i = line.indexOf(' ');
if (i == GIT_ID_LENGTH) {
tag.id = line.mid(0, i);
if (MidCmp(line, i, " refs/tags/")) {
tag.name = line.mid(i + 11).trimmed();
if (tag.name.isEmpty()) continue;
list.push_back(tag);
}
}
}
return list;
#else
return tags2();
#endif
}
QList<Git::Tag> Git::tags2()
{
QList<Tag> list;
git("show-ref");
QStringList lines = misc::splitLines(resultText());
for (QString const &line : lines) {
QStringList l = misc::splitWords(line);
if (l.size() >= 2) {
if (isValidID(l[0]) && l[1].startsWith("refs/tags/")) {
Tag t;
t.name = l[1].mid(10);
t.id = l[0];
list.push_back(t);
}
}
}
return list;
}
bool Git::tag(QString const &name, QString const &id)
{
QString cmd = "tag \"%1\" %2";
cmd = cmd.arg(name).arg(id);
return git(cmd);
}
void Git::delete_tag(QString const &name, bool remote)
{
QString cmd = "tag --delete \"%1\"";
cmd = cmd.arg(name);
git(cmd);
if (remote) {
QString cmd = "push --delete origin \"%1\"";
cmd = cmd.arg(name);
git(cmd);
}
}
QString Git::diff(QString const &old_id, QString const &new_id)
{
QString cmd = "diff --full-index -a %1 %2";
cmd = cmd.arg(old_id).arg(new_id);
git(cmd);
return resultText();
}
QString Git::diff_file(QString const &old_path, QString const &new_path)
{
QString cmd = "diff --full-index -a -- %1 %2";
cmd = cmd.arg(old_path).arg(new_path);
git(cmd);
return resultText();
}
QList<Git::DiffRaw> Git::diff_raw(QString const &old_id, QString const &new_id)
{
QList<DiffRaw> list;
QString cmd = "diff --raw --abbrev=%1 %2 %3";
cmd = cmd.arg(GIT_ID_LENGTH).arg(old_id).arg(new_id);
git(cmd);
QString text = resultText();
QStringList lines = text.split('\n', QString::SkipEmptyParts);
for (QString const &line : lines) { // raw format: e.g. ":100644 100644 08bc10d... 18f0501... M src/MainWindow.cpp"
DiffRaw item;
int colon = line.indexOf(':');
int tab = line.indexOf('\t');
if (colon >= 0 && colon < tab) {
QStringList header = line.mid(colon + 1, tab - colon - 1).split(' ', QString::SkipEmptyParts); // コロンとタブの間の文字列を空白で分割
if (header.size() >= 5) {
QStringList files = line.mid(tab + 1).split('\t', QString::SkipEmptyParts); // タブより後ろはファイルパス
if (!files.empty()) {
for (QString &file : files) {
file = Git::trimPath(file);
}
item.a.id = header[2];
item.b.id = header[3];
item.a.mode = header[0];
item.b.mode = header[1];
item.state = header[4];
item.files = files;
list.push_back(item);
}
}
}
}
return list;
}
QString Git::diff_to_file(QString const &old_id, QString const &path)
{
#if 1
QString cmd = "diff --full-index -a %1 -- \"%2\"";
cmd = cmd.arg(old_id).arg(path);
git(cmd);
return resultText();
#else
return diff_(old_id, new_id);
#endif
}
QString Git::getCurrentBranchName()
{
if (isValidWorkingCopy()) {
git("rev-parse --abbrev-ref HEAD");
QString s = resultText().trimmed();
if (!s.isEmpty() && !s.startsWith("fatal:") && s.indexOf(' ') < 0) {
return s;
}
}
return QString();
}
QStringList Git::getUntrackedFiles()
{
QStringList files;
Git::FileStatusList stats = status_s();
for (FileStatus const &s : stats) {
if (s.code() == FileStatusCode::Untracked) {
files.push_back(s.path1());
}
}
return files;
}
void Git::parseAheadBehind(QString const &s, Branch *b)
{
ushort const *begin = s.utf16();
ushort const *end = begin + s.size();
ushort const *ptr = begin;
auto NCompare = [](ushort const *a, char const *b, size_t n){
for (size_t i = 0; i < n; i++) {
if (*a < (unsigned char)*b) return false;
if (*a > (unsigned char)*b) return false;
if (!*a) break;
a++;
b++;
}
return true;
};
auto SkipSpace = [&](){
while (ptr < end && QChar(*ptr).isSpace()) {
ptr++;
}
};
auto GetNumber = [&](){
int n = 0;
while (ptr < end && QChar(*ptr).isDigit()) {
n = n * 10 + (*ptr - '0');
ptr++;
}
return n;
};
if (*ptr == '[') {
ptr++;
ushort const *e = nullptr;
int n;
for (n = 0; ptr + n < end; n++) {
if (ptr[n] == ']') {
e = ptr + n;
break;
}
}
if (e) {
end = e;
while (1) {
if (NCompare(ptr, "ahead ", 6)) {
ptr += 6;
SkipSpace();
b->ahead = GetNumber();
} else if (NCompare(ptr, "behind ", 7)) {
ptr += 7;
SkipSpace();
b->behind = GetNumber();
} else {
ushort c = 0;
if (ptr < end) {
c = *ptr;
}
if (c == 0) break;
ptr++;
}
}
}
}
}
QList<Git::Branch> Git::branches()
{
QList<Branch> branches;
git(QString("branch -vv -a --abbrev=%1").arg(GIT_ID_LENGTH));
QString s = resultText();
#if DEBUGLOG
qDebug() << s;
#endif
QStringList lines = misc::splitLines(s);
for (QString const &line : lines) {
if (line.size() > 2) {
QString name;
QString text = line.mid(2);
int pos = 0;
if (text.startsWith('(')) {
int i, paren = 1;
for (i = 1; i < text.size(); i++) {
if (text[i] == '(') {
paren++;
} else if (text[i] == ')') {
if (paren > 0) {
paren--;
if (paren == 0) {
pos = i;
break;
}
}
}
}
// pos = text.indexOf(')');
if (pos > 1) {
name = text.mid(1, pos - 1);
pos++;
}
} else {
while (pos < text.size() && !QChar::isSpace(text.utf16()[pos])) {
pos++;
}
name = text.mid(0, pos);
}
if (!name.isEmpty()) {
while (pos < text.size() && QChar::isSpace(text.utf16()[pos])) {
pos++;
}
text = text.mid(pos);
Branch b;
if (name.startsWith("HEAD detached at ")) {
b.flags |= Branch::HeadDetachedAt;
name = name.mid(17);
}
if (name.startsWith("HEAD detached from ")) {
b.flags |= Branch::HeadDetachedFrom;
name = name.mid(19);
}
if (name.startsWith("remotes/")) {
name = name.mid(8);
int i = name.indexOf('/');
if (i > 0) {
b.remote = name.mid(0, i);
name = name.mid(i + 1);
}
}
b.name = name;
if (text.startsWith("-> ")) {
b.id = ">" + text.mid(3);
} else {
int i = text.indexOf(' ');
if (i == GIT_ID_LENGTH) {
b.id = text.mid(0, GIT_ID_LENGTH);
}
while (i < text.size() && QChar::isSpace(text.utf16()[i])) {
i++;
}
text = text.mid(i);
parseAheadBehind(text, &b);
}
if (line.startsWith('*')) {
b.flags |= Branch::Current;
}
branches.push_back(b);
}
}
}
for (int i = 0; i < branches.size(); i++) {
Branch *b = &branches[i];
if (b->id.startsWith('>')) {
QString name = b->id.mid(1);
for (int j = 0; j < branches.size(); j++) {
if (j != i) {
if (branches[j].name == name) {
branches[i].id = branches[j].id;
break;
}
}
}
}
}
return branches;
}
Git::CommitItemList Git::log_all(QString const &id, int maxcount)
{
CommitItemList items;
QString text;
QString cmd = "log --pretty=format:\"commit:%H#gpg:%G?#key:%GK#parent:%P#author:%an#mail:%ae#date:%ci##%s\" --all -%1 %2";
cmd = cmd.arg(maxcount).arg(id);
git(cmd);
if (getProcessExitCode() == 0) {
text = resultText().trimmed();
QStringList lines = misc::splitLines(text);
for (QString const &line : lines) {
int i = line.indexOf("##");
if (i > 0) {
Git::CommitItem item;
QString signed_key;
item.message = line.mid(i + 2);
QStringList atts = line.mid(0, i).split('#');
for (QString const &s : atts) {
int j = s.indexOf(':');
if (j > 0) {
QString key = s.mid(0, j);
QString val = s.mid(j + 1);
if (key == "commit") {
item.commit_id = val;
} else if (key == "gpg") {
item.signature = *val.utf16();
} else if (key == "key") {
signed_key = val;
} else if (key == "parent") {
item.parent_ids = val.split(' ', QString::SkipEmptyParts);
} else if (key == "author") {
item.author = val;
} else if (key == "mail") {
item.email = val;
} else if (key == "date") {
item.commit_date = QDateTime::fromString(val, Qt::ISODate).toLocalTime();
} else if (key == "debug") {
}
}
}
if (!signed_key.isEmpty()) {
ushort const *begin = signed_key.utf16();
int n = signed_key.size();
for (int i = 0; i + 1 < n; i += 2) {
ushort c = begin[i];
ushort d = begin[i + 1];
if (c < 0x80 && isxdigit(c) && isxdigit(d)) {
char tmp[3];
tmp[0] = c;
tmp[1] = d;
tmp[2] = 0;
int v = strtol(tmp, nullptr, 16);
item.fingerprint.push_back(v);
} else {
break;
}
}
}
items.push_back(item);
}
}
}
return items;
}
Git::CommitItemList Git::log(int maxcount)
{
return log_all(QString(), maxcount);
}
bool Git::queryCommit(QString const &id, CommitItem *out)
{
*out = {};
if (objectType(id) == "commit") {
out->commit_id = id;
QByteArray ba;
if (cat_file(id, &ba)) {
QStringList lines = misc::splitLines(ba, [](char const *p, size_t n){
return QString::fromUtf8(p, (int)n);
});
while (!lines.empty() && lines[lines.size() - 1].isEmpty()) {
lines.pop_back();
}
int i;
for (i = 0; i < lines.size(); i++) {
QString const &line = lines[i];
if (line.isEmpty()) {
i++;
for (; i < lines.size(); i++) {
QString const &line = lines[i];
if (!out->message.isEmpty()) {
out->message.append('\n');
}
out->message.append(line);
}
break;
}
if (line.startsWith("parent ")) {
out->parent_ids.push_back(line.mid(7));
} else if (line.startsWith("author ")) {
QStringList arr = misc::splitWords(line);
int n = arr.size();
if (n > 4) {
n -= 2;
out->commit_date = QDateTime::fromTime_t(atol(arr[n].toStdString().c_str()));
n--;
out->email = arr[n];
if (out->email.startsWith('<') && out->email.endsWith('>')) {
int n = out->email.size();
out->email = out->email.mid(1, n - 2);
}
for (int i = 1; i < n; i++) {
if (!out->author.isEmpty()) {
out->author += ' ';
}
out->author += arr[i];
}
}
}
}
}
return true;
}
return false;
}
Git::CloneData Git::preclone(QString const &url, QString const &path)
{
CloneData d;
d.url = url;
if (path.endsWith('/') || path.endsWith('\\')) {
auto GitBaseName = [](QString location){
int i = location.lastIndexOf('/');
int j = location.lastIndexOf('\\');
if (i < j) i = j;
i = i < 0 ? 0 : (i + 1);
j = location.size();
if (location.endsWith(".git")) {
j -= 4;
}
if (i < j) {
location = location.mid(i, j - i);
}
return location;
};
d.basedir = path;
d.subdir = GitBaseName(url);
} else {
QFileInfo info(path);
d.basedir = info.dir().path();
d.subdir = info.fileName();
}
return d;
}
bool Git::clone(CloneData const &data, AbstractPtyProcess *pty)
{
QString clone_to = data.basedir / data.subdir;
m->info.working_repo_dir = misc::normalizePathSeparator(clone_to);
bool ok = false;
QDir cwd = QDir::current();
auto DoIt = [&](){
QString cmd = "clone --progress \"%1\" \"%2\"";
cmd = cmd.arg(data.url).arg(data.subdir);
ok = git(cmd, false, true, pty);
};
if (pty) {
pty->setChangeDir(data.basedir);
DoIt();
} else {
if (QDir::setCurrent(data.basedir)) {
DoIt();
QDir::setCurrent(cwd.path());
}
}
return ok;
}
QList<Git::SubmoduleItem> Git::submodules()
{
QList<Git::SubmoduleItem> mods;
git("submodule");
QString text = resultText();
ushort c = text.utf16()[0];
if (c == ' ' || c == '+' || c == '-') {
text = text.mid(1);
}
QStringList words = misc::splitWords(text);
if (words.size() >= 2) {
SubmoduleItem sm;
sm.id = words[0];
sm.path = words[1];
if (isValidID(sm.id)) {
if (words.size() >= 3) {
sm.refs = words[2];
if (sm.refs.startsWith('(') && sm.refs.endsWith(')')) {
sm.refs = sm.refs.mid(1, sm.refs.size() - 2);
}
}
mods.push_back(sm);
}
}
return mods;
}
bool Git::submodule_add(const CloneData &data, bool force, AbstractPtyProcess *pty)
{
bool ok = false;
QString cmd = "submodule add";
if (force) {
cmd += " -f";
}
cmd += " \"%1\" \"%2\"";
cmd = cmd.arg(data.url).arg(data.subdir);
ok = git(cmd, true, true, pty);
return ok;
}
bool Git::submodule_update(SubmoduleUpdateData const &data, AbstractPtyProcess *pty)
{
bool ok = false;
QString cmd = "submodule update";
if (data.init) {
cmd += " --init";
}
if (data.recursive) {
cmd += " --recursive";
}
ok = git(cmd, true, true, pty);
return ok;
}
QString Git::encodeQuotedText(QString const &str)
{
std::vector<ushort> vec;
ushort const *begin = str.utf16();
ushort const *end = begin + str.size();
ushort const *ptr = begin;
vec.push_back('\"');
while (ptr < end) {
ushort c = *ptr;
ptr++;
if (c == '\"') { // triple quotes
vec.push_back(c);
vec.push_back(c);
vec.push_back(c);
} else {
vec.push_back(c);
}
}
vec.push_back('\"');
return QString::fromUtf16(&vec[0], vec.size());
}
bool Git::commit_(QString const &msg, bool amend, bool sign, AbstractPtyProcess *pty)
{
QString cmd = "commit";
if (amend) {
cmd += " --amend";
}
if (sign) {
cmd += " -S";
}
QString text = msg.trimmed();
if (text.isEmpty()) {
text = "no message";
}
text = encodeQuotedText(text);
cmd += QString(" -m %1").arg(text);
return git(cmd, true, false, pty);
}
bool Git::commit(QString const &text, bool sign, AbstractPtyProcess *pty)
{
return commit_(text, false, sign, pty);
}
bool Git::commit_amend_m(QString const &text, bool sign, AbstractPtyProcess *pty)
{
return commit_(text, true, sign, pty);
}
bool Git::revert(QString const &id)
{
QString cmd = "revert %1";
cmd = cmd.arg(id);
return git(cmd);
}
void Git::push_u(QString const &remote, QString const &branch, AbstractPtyProcess *pty)
{
if (remote.indexOf('\"') >= 0 || branch.indexOf('\"') >= 0) {
return;
}
QString cmd = "push -u \"%1\" \"%2\"";
git(cmd.arg(remote).arg(branch), true, false, pty);
}
bool Git::push_(bool tags, AbstractPtyProcess *pty)
{
QString cmd = "push";
if (tags) {
cmd += " --tags";
}
return git(cmd, true, false, pty);
}
bool Git::push(bool tags, AbstractPtyProcess *pty)
{
return push_(tags, pty);
}
Git::FileStatusList Git::status_s_()
{
FileStatusList files;
if (git("status -s -u --porcelain")) {
QString text = resultText();
QStringList lines = misc::splitLines(text);
for (QString const &line : lines) {
if (line.size() > 3) {
FileStatus s(line);
if (s.code() != FileStatusCode::Unknown) {
files.push_back(s);
}
}
}
}
return files;
}
Git::FileStatusList Git::status_s()
{
return status_s_();
}
QString Git::objectType(QString const &id)
{
if (isValidID(id)) {
git("cat-file -t " + id);
return resultText().trimmed();
}
return {};
}
QByteArray Git::cat_file_(QString const &id)
{
if (isValidID(id)) {
git("cat-file -p " + id);
return toQByteArray();
}
return {};
}
bool Git::cat_file(QString const &id, QByteArray *out)
{
if (isValidID(id)) {
*out = cat_file_(id);
return true;
}
return false;
}
void Git::resetFile(QString const &path)
{
git("checkout -- " + path);
}
void Git::resetAllFiles()
{
git("reset --hard HEAD");
}
void Git::removeFile(QString const &path)
{
git("rm " + path);
}
void Git::stage(QString const &path)
{
git("add " + path);
}
void Git::stage(QStringList const &paths)
{
QString cmd = "add";
for (QString const &path : paths) {
cmd += ' ';
cmd += '\"';
cmd += path;
cmd += '\"';
}
git(cmd);
}
void Git::unstage(QString const &path)
{
QString cmd = "reset HEAD \"%1\"";
git(cmd.arg(path));
}
void Git::unstage(QStringList const &paths)
{
QString cmd = "reset HEAD";
for (QString const &path : paths) {
cmd += " \"";
cmd += path;
cmd += '\"';
}
git(cmd);
}
void Git::pull(AbstractPtyProcess *pty)
{
git("pull", true, false, pty);
}
void Git::fetch(AbstractPtyProcess *pty, bool prune)
{
QString cmd = "fetch --tags -f";
if (prune) {
cmd += " --prune";
}
git(cmd, true, false, pty);
}
void Git::fetch_tags_f(AbstractPtyProcess *pty)
{
QString cmd = "fetch --tags -f";
git(cmd, true, false, pty);
}
QStringList Git::make_branch_list_()
{
QStringList list;
QStringList l = misc::splitLines(resultText());
for (QString const &s : l) {
if (s.startsWith("* ")) list.push_back(s.mid(2));
if (s.startsWith(" ")) list.push_back(s.mid(2));
}
return list;
}
void Git::createBranch(QString const &name)
{
git("branch " + name);
}
void Git::checkoutBranch(QString const &name)
{
git("checkout " + name);
}
void Git::cherrypick(QString const &name)
{
git("cherry-pick " + name);
}
QString Git::getCherryPicking() const
{
QString dir = workingDir();
QString path = dir / ".git/CHERRY_PICK_HEAD";
QFile file(path);
if (file.open(QFile::ReadOnly)) {
QString line = QString::fromLatin1(file.readLine()).trimmed();
if (isValidID(line)) {
return line;
}
}
return QString();
}
QString Git::getMessage(QString const &id)
{
git("show --no-patch --pretty=%s " + id);
return resultText().trimmed();
}
-void Git::mergeBranch(QString const &name, MergeFastForward ff)
+void Git::mergeBranch(QString const &name, MergeFastForward ff, bool squash)
{
QString cmd = "merge ";
switch (ff) {
case MergeFastForward::No:
cmd += "--no-ff ";
break;
case MergeFastForward::Only:
cmd += "--ff-only ";
break;
default:
cmd += "--ff ";
break;
}
+ if (squash) {
+ cmd += "--squash ";
+ }
git(cmd + name);
}
void Git::rebaseBranch(QString const &name)
{
git("rebase " + name);
}
QStringList Git::getRemotes()
{
QStringList ret;
git("remote");
QStringList lines = misc::splitLines(resultText());
for (QString const &line: lines) {
if (!line.isEmpty()) {
ret.push_back(line);
}
}
return ret;
}
Git::User Git::getUser(Source purpose)
{
User user;
bool global = purpose == Git::Source::Global;
bool local = purpose == Git::Source::Local;
QString arg1;
if (global) arg1 = "--global";
if (local) arg1 = "--local";
bool chdir = !global;
if (git(QString("config %1 user.name").arg(arg1), chdir)) {
user.name = resultText().trimmed();
}
if (git(QString("config %1 user.email").arg(arg1), chdir)) {
user.email = resultText().trimmed();
}
return user;
}
void Git::setUser(const User &user, bool global)
{
bool chdir = !global;
git(QString("config %1 user.name %2").arg(global ? "--global" : "").arg(encodeQuotedText(user.name)), chdir);
git(QString("config %1 user.email %2").arg(global ? "--global" : "").arg(encodeQuotedText(user.email)), chdir);
}
bool Git::reset_head1()
{
return git("reset HEAD~1");
}
bool Git::reset_hard()
{
return git("reset --hard");
}
bool Git::clean_df()
{
return git("clean -df");
}
bool Git::stash()
{
return git("stash");
}
bool Git::stash_apply()
{
return git("stash apply");
}
bool Git::stash_drop()
{
return git("stash drop");
}
bool Git::rm_cached(QString const &file)
{
QString cmd = "rm --cached \"%1\"";
return git(cmd.arg(file));
}
void Git::getRemoteURLs(QList<Remote> *out)
{
out->clear();
git("remote -v");
QStringList lines = misc::splitLines(resultText());
for (QString const &line : lines) {
int i = line.indexOf('\t');
int j = line.indexOf(" (");
if (i > 0 && i < j) {
Remote r;
r.name = line.mid(0, i);
r.url = line.mid(i + 1, j - i - 1);
r.purpose = line.mid(j + 1);
r.ssh_key = m->ssh_key_override;
if (r.purpose.startsWith('(') && r.purpose.endsWith(')')) {
r.purpose = r.purpose.mid(1, r.purpose.size() - 2);
}
out->push_back(r);
}
}
}
void Git::setRemoteURL(Git::Remote const &remote)
{
QString cmd = "remote set-url %1 %2";
cmd = cmd.arg(encodeQuotedText(remote.name)).arg(encodeQuotedText(remote.url));
git(cmd);
}
void Git::addRemoteURL(Git::Remote const &remote)
{
QString cmd = "remote add \"%1\" \"%2\"";
cmd = cmd.arg(encodeQuotedText(remote.name)).arg(encodeQuotedText(remote.url));
m->ssh_key_override = remote.ssh_key;
git(cmd);
}
void Git::removeRemote(QString const &name)
{
QString cmd = "remote remove %1";
cmd = cmd.arg(encodeQuotedText(name));
git(cmd);
}
bool Git::reflog(ReflogItemList *out, int maxcount)
{
out->clear();
QString cmd = "reflog --no-abbrev --raw -n %1";
cmd = cmd.arg(maxcount);
if (!git(cmd)) return false;
QByteArray ba = toQByteArray();
if (!ba.isEmpty()) {
ReflogItem item;
char const *begin = ba.data();
char const *end = begin + ba.size();
char const *left = begin;
char const *ptr = begin;
while (1) {
int c = 0;
if (ptr < end) {
c = *ptr;
}
if (c == '\r' || c == '\n' || c == 0) {
int d = 0;
QString line = QString::fromUtf8(left, ptr - left);
if (left < ptr) {
d = *left & 0xff;
}
if (d == ':') {
// ex. ":100644 100644 bb603836fb597cca994309a1f0a52251d6b20314 d6b9798854debee375bb419f0f2ed9c8437f1932 M\tsrc/MainWindow.cpp"
int tab = line.indexOf('\t');
if (tab > 1) {
QString tmp = line.mid(1, tab - 1);
QString path = line.mid(tab + 1);
QStringList cols = misc::splitWords(tmp);
if (!path.isEmpty() && cols.size() == 5) {
ReflogItem::File file;
file.atts_a = cols[0];
file.atts_b = cols[1];
file.id_a = cols[2];
file.id_b = cols[3];
file.type = cols[4];
file.path = path;
item.files.push_back(file);
}
}
} else {
bool start = isxdigit(d);
if (start || c == 0) {
if (!item.id.isEmpty()) {
out->push_back(item);
}
}
if (start) {
// ex. "0a2a8b6b66f48bcbf985d8a2afcd14ff41676c16 HEAD@{188}: commit: message"
item = ReflogItem();
int i = line.indexOf(": ");
if (i > 0) {
int j = line.indexOf(": ", i + 2);
if (j > 2) {
item.head = line.mid(0, i);
item.command = line.mid(i + 2, j - i - 2);
item.message = line.mid(j + 2);
if (item.head.size() > GIT_ID_LENGTH) {
item.id = item.head.mid(0, GIT_ID_LENGTH);
item.head = item.head.mid(GIT_ID_LENGTH + 1);
}
}
}
}
}
if (c == 0) break;
ptr++;
left = ptr;
} else {
ptr++;
}
}
}
return true;
}
// Git::FileStatus
QString Git::trimPath(QString const &s)
{
ushort const *begin = s.utf16();
ushort const *end = begin + s.size();
ushort const *left = begin;
ushort const *right = end;
while (left < right && QChar(*left).isSpace()) left++;
while (left < right && QChar(right[-1]).isSpace()) right--;
if (left + 1 < right && *left == '\"' && right[-1] == '\"') { // if quoted ?
left++;
right--;
QByteArray ba;
ushort const *ptr = left;
while (ptr < right) {
ushort c = *ptr;
ptr++;
if (c == '\\') {
c = 0;
while (ptr < right && QChar(*ptr).isDigit()) { // decode \oct
c = c * 8 + (*ptr - '0');
ptr++;
}
}
ba.push_back(c);
}
return QString::fromUtf8(ba);
}
if (left == begin && right == end) return s;
return QString::fromUtf16(left, right - left);
}
Git::FileStatusCode Git::FileStatus::parseFileStatusCode(char x, char y)
{
if (x == ' ' && (y == 'M' || y == 'D')) return FileStatusCode::NotUpdated;
if (x == 'M' && (y == 'M' || y == 'D' || y == ' ')) return FileStatusCode::UpdatedInIndex;
if (x == 'A' && (y == 'M' || y == 'D' || y == ' ')) return FileStatusCode::AddedToIndex;
if (x == 'D' && (y == 'M' || y == ' ')) return FileStatusCode::DeletedFromIndex;
if (x == 'R' && (y == 'M' || y == 'D' || y == ' ')) return FileStatusCode::RenamedInIndex;
if (x == 'C' && (y == 'M' || y == 'D' || y == ' ')) return FileStatusCode::CopiedInIndex;
if (x == 'C' && (y == 'M' || y == 'D' || y == ' ')) return FileStatusCode::CopiedInIndex;
if (x == 'D' && y == 'D') return FileStatusCode::Unmerged_BothDeleted;
if (x == 'A' && y == 'U') return FileStatusCode::Unmerged_AddedByUs;
if (x == 'U' && y == 'D') return FileStatusCode::Unmerged_DeletedByThem;
if (x == 'U' && y == 'A') return FileStatusCode::Unmerged_AddedByThem;
if (x == 'D' && y == 'U') return FileStatusCode::Unmerged_DeletedByUs;
if (x == 'A' && y == 'A') return FileStatusCode::Unmerged_BothAdded;
if (x == 'U' && y == 'U') return FileStatusCode::Unmerged_BothModified;
if (x == '?' && y == '?') return FileStatusCode::Untracked;
if (x == '!' && y == '!') return FileStatusCode::Ignored;
return FileStatusCode::Unknown;
}
void Git::FileStatus::parse(QString const &text)
{
data = Data();
if (text.size() > 3) {
ushort const *p = text.utf16();
if (p[2] == ' ') {
data.code_x = p[0];
data.code_y = p[1];
int i = text.indexOf(" -> ", 3);
if (i > 3) {
data.rawpath1 = text.mid(3, i - 3);
data.rawpath2 = text.mid(i + 4);
data.path1 = trimPath(data.rawpath1);
data.path2 = trimPath(data.rawpath2);
} else {
data.rawpath1 = text.mid(3);
data.path1 = trimPath(data.rawpath1);
data.path2 = QString();
}
data.code = parseFileStatusCode(data.code_x, data.code_y);
}
}
}
QByteArray Git::blame(QString const &path)
{
QString cmd = "blame --porcelain --abbrev=40 \"%1\"";
cmd = cmd.arg(path);
if (git(cmd)) {
return toQByteArray();
}
return QByteArray();
}
QList<Git::RemoteInfo> Git::ls_remote()
{
QList<RemoteInfo> list;
QString cmd = "ls-remote";
if (git(cmd)) {
QStringList lines = misc::splitLines(resultText());
for (QString const &line : lines) {
QStringList words = misc::splitWords(line);
if (words.size() == 2 && isValidID(words[0])) {
RemoteInfo info;
info.commit_id = words[0];
info.name = words[1];
list.push_back(info);
}
}
}
return list;
}
QString Git::signingKey(Source purpose)
{
QString arg1;
if (purpose == Source::Global) arg1 = "--global";
if (purpose == Source::Local) arg1 = "--local";
QString cmd = "config %1 user.signingkey";
cmd = cmd.arg(arg1);
bool chdir = purpose != Source::Global;
if (git(cmd, chdir)) {
return resultText().trimmed();
}
return QString();
}
bool Git::setSigningKey(QString const &id, bool global)
{
for (auto i : id) {
if (!QChar(i).isLetterOrNumber()) return false;
}
QString cmd = "config %1 %2 user.signingkey %3";
cmd = cmd.arg(global ? "--global" : "--local").arg(id.isEmpty() ? "--unset" : "").arg(id);
return git(cmd, !global);
}
Git::SignPolicy Git::signPolicy(Source source)
{
QString arg1;
if (source == Source::Global) arg1 = "--global";
if (source == Source::Local) arg1 = "--local";
QString cmd = "config %1 commit.gpgsign";
cmd = cmd.arg(arg1);
bool chdir = source != Source::Global;
if (git(cmd, chdir)) {
QString t = resultText().trimmed();
if (t == "false") return SignPolicy::False;
if (t == "true") return SignPolicy::True;
}
return SignPolicy::Unset;
}
bool Git::setSignPolicy(Source source, SignPolicy policy)
{
QString arg1;
if (source == Source::Global) arg1 = "--global";
if (source == Source::Local) arg1 = "--local";
QString cmd = "config %1 %2 commit.gpgsign %3";
QString arg2;
QString arg3;
if (policy == SignPolicy::False) {
arg3 = "false";
} else if (policy == SignPolicy::True) {
arg3 = "true";
} else {
arg2 = "--unset";
}
cmd = cmd.arg(arg1).arg(arg2).arg(arg3);
bool chdir = source != Source::Global;
return git(cmd, chdir);
}
bool Git::configGpgProgram(QString const &path, bool global)
{
QString cmd = "config ";
if (global) {
cmd += "--global ";
}
if (path.isEmpty()) {
cmd += "--unset ";
}
cmd += "gpg.program ";
if (!path.isEmpty()) {
cmd += QString("\"%1\"").arg(path);
}
return git(cmd, false);
}
// Diff
void Git::Diff::makeForSingleFile(Git::Diff *diff, QString const &id_a, QString const &id_b, QString const &path, QString const &mode)
{
diff->diff = QString("diff --git a/%1 b/%2").arg(path).arg(path);
diff->index = QString("index %1..%2 %3").arg(id_a).arg(id_b).arg(0);
diff->blob.a_id = id_a;
diff->blob.b_id = id_b;
diff->path = path;
diff->mode = mode;
diff->type = Git::Diff::Type::Create;
}
//
void parseDiff(std::string const &s, Git::Diff const *info, Git::Diff *out)
{
std::vector<std::string> lines;
{
char const *begin = s.c_str();
char const *end = begin + s.size();
misc::splitLines(begin, end, &lines, false);
}
out->diff = QString("diff --git ") + ("a/" + info->path) + ' ' + ("b/" + info->path);
out->index = QString("index ") + info->blob.a_id + ".." + info->blob.b_id + ' ' + info->mode;
out->path = info->path;
out->blob = info->blob;
bool atat = false;
for (std::string const &line : lines) {
int c = line[0] & 0xff;
if (c == '@') {
if (strncmp(line.c_str(), "@@ ", 3) == 0) {
out->hunks.push_back(Git::Hunk());
out->hunks.back().at = line;
atat = true;
}
} else {
if (atat && c == '\\') { // e.g. \ No newline at end of file...
// ignore this line
} else {
if (atat) {
if (c == ' ' || c == '-' || c == '+') {
// nop
} else {
atat = false;
}
}
if (atat) {
if (!out->hunks.isEmpty()) {
out->hunks.back().lines.push_back(line);
}
}
}
}
}
}
void parseGitSubModules(const QByteArray &ba, QList<Git::SubmoduleItem> *out)
{
*out = {};
QStringList lines = misc::splitLines(QString::fromUtf8(ba));
Git::SubmoduleItem submod;
auto Push = [&](){
if (!submod.name.isEmpty()) {
out->push_back(submod);
}
submod = {};
};
for (int i = 0; i < lines.size(); i++) {
QString line = lines[i].trimmed();
if (line.startsWith('[')) {
Push();
if (line.startsWith("[submodule ") && line.endsWith(']')) {
int i = 11;
int j = line.size() - 1;
if (i + 1 < j && line[i] == '\"') {
if (line[j - 1] == '\"') {
i++;
j--;
}
}
submod.name = line.mid(i, j - i);
}
} else {
int i = line.indexOf('=');
if (i > 0) {
QString key = line.mid(0, i).trimmed();
QString val = line.mid(i + 1).trimmed();
if (key == "path") {
submod.path = val;
} else if (key == "url") {
submod.url = val;
}
}
}
}
Push();
}
diff --git a/src/Git.h b/src/Git.h
index 35f813f..b5c10c8 100644
--- a/src/Git.h
+++ b/src/Git.h
@@ -1,533 +1,533 @@
#ifndef GIT_H
#define GIT_H
#include "AbstractProcess.h"
#include <QDateTime>
#include <QObject>
#include <functional>
#include <QDebug>
#include <QMutex>
#include <memory>
#define SINGLE_THREAD 0
#define GIT_ID_LENGTH (40)
class Win32PtyProcess;
enum class LineSide {
Left,
Right,
};
struct TreeLine {
int index;
int depth;
int color_number = 0;
bool bend_early = false;
TreeLine(int index = -1, int depth = -1)
: index(index)
, depth(depth)
{
}
};
struct NamedCommitItem {
enum class Type {
None,
BranchLocal,
BranchRemote,
Tag,
};
Type type = Type::None;
QString remote;
QString name;
QString id;
};
using NamedCommitList = QList<NamedCommitItem>;
class Git;
using GitPtr = std::shared_ptr<Git>;
class Git : QObject {
public:
class Context {
public:
QString git_command;
QString ssh_command;// = "C:/Program Files/Git/usr/bin/ssh.exe";
};
struct Object {
enum class Type {
UNKNOWN = 0,
COMMIT = 1,
TREE = 2,
BLOB = 3,
TAG = 4,
UNDEFINED = 5,
OFS_DELTA = 6,
REF_DELTA = 7,
};
Type type = Type::UNKNOWN;
QByteArray content;
};
struct SubmoduleItem {
QString name;
QString id;
QString path;
QString refs;
QString url;
operator bool () const
{
return isValidID(id) && !path.isEmpty();
}
};
struct CommitItem {
QString commit_id;
QStringList parent_ids;
QString author;
QString email;
QString message;
QDateTime commit_date;
std::vector<TreeLine> parent_lines;
QByteArray fingerprint;
char signature = 0; // git log format:%G?
bool has_child = false;
int marker_depth = -1;
bool resolved = false;
bool strange_date = false;
};
using CommitItemList = std::vector<CommitItem>;
class Hunk {
public:
std::string at;
std::vector<std::string> lines;
};
class Diff {
public:
enum class Type {
Unknown,
Modify,
Copy,
Rename,
Create,
Delete,
ChType,
Unmerged,
};
Type type = Type::Unknown;
QString diff;
QString index;
QString path;
QString mode;
struct BLOB_AB_ {
QString a_id;
QString b_id;
} blob;
QList<Hunk> hunks;
struct SubmoduleDetail {
Git::SubmoduleItem item;
Git::CommitItem commit;
} a_submodule, b_submodule;
Diff() = default;
Diff(QString const &id, QString const &path, QString const &mode)
{
makeForSingleFile(this, QString(GIT_ID_LENGTH, '0'), id, path, mode);
}
bool isSubmodule() const
{
return mode == "160000";
}
private:
void makeForSingleFile(Git::Diff *diff, QString const &id_a, QString const &id_b, QString const &path, QString const &mode);
};
enum class SignatureGrade {
NoSignature,
Unknown,
Good,
Dubious,
Missing,
Bad,
};
static SignatureGrade evaluateSignature(char s)
{
switch (s) {
case 'G':
return SignatureGrade::Good;
case 'U':
case 'X':
case 'Y':
return SignatureGrade::Dubious;
case 'B':
case 'R':
return SignatureGrade::Bad;
case 'E':
return SignatureGrade::Missing;
case 'N':
case ' ':
case 0:
return SignatureGrade::NoSignature;
}
return SignatureGrade::Unknown;
}
static bool isUncommited(CommitItem const &item)
{
return item.commit_id.isEmpty();
}
struct Branch {
QString name;
QString id;
QString remote;
int ahead = 0;
int behind = 0;
enum {
None,
Current = 0x0001,
HeadDetachedAt = 0x0002,
HeadDetachedFrom = 0x0004,
};
int flags = 0;
operator bool () const
{
if (name.isEmpty()) return false;
if (id.isEmpty()) return false;
return true;
}
bool isCurrent() const
{
return flags & Current;
}
bool isHeadDetached() const
{
return flags & HeadDetachedAt;
}
};
struct Tag {
QString name;
QString id;
};
enum class FileStatusCode : unsigned int {
Unknown,
Ignored,
Untracked,
NotUpdated = 0x10000000,
Staged_ = 0x20000000,
UpdatedInIndex,
AddedToIndex,
DeletedFromIndex,
RenamedInIndex,
CopiedInIndex,
Unmerged_ = 0x40000000,
Unmerged_BothDeleted,
Unmerged_AddedByUs,
Unmerged_DeletedByThem,
Unmerged_AddedByThem,
Unmerged_DeletedByUs,
Unmerged_BothAdded,
Unmerged_BothModified,
Tracked_ = 0xf0000000
};
enum class MergeFastForward {
Default,
No,
Only,
};
class FileStatus {
public:
struct Data {
char code_x = 0;
char code_y = 0;
FileStatusCode code = FileStatusCode::Unknown;
QString rawpath1;
QString rawpath2;
QString path1;
QString path2;
} data;
static FileStatusCode parseFileStatusCode(char x, char y);
bool isStaged() const
{
return (int)data.code & (int)FileStatusCode::Staged_;
}
bool isUnmerged() const
{
return (int)data.code & (int)FileStatusCode::Unmerged_;
}
bool isTracked() const
{
return (int)data.code & (int)FileStatusCode::Tracked_;
}
void parse(QString const &text);
FileStatus() = default;
FileStatus(QString const &text)
{
parse(text);
}
FileStatusCode code() const
{
return data.code;
}
int code_x() const
{
return data.code_x;
}
int code_y() const
{
return data.code_y;
}
bool isDeleted() const
{
return code_x() == 'D' || code_y() == 'D';
}
QString path1() const
{
return data.path1;
}
QString path2() const
{
return data.path2;
}
QString rawpath1() const
{
return data.rawpath1;
}
QString rawpath2() const
{
return data.rawpath2;
}
};
using FileStatusList = std::vector<FileStatus>;
static QString trimPath(QString const &s);
private:
struct Private;
Private *m;
QStringList make_branch_list_();
QByteArray cat_file_(QString const &id);
FileStatusList status_s_();
bool commit_(QString const &msg, bool amend, bool sign, AbstractPtyProcess *pty);
bool push_(bool tags, AbstractPtyProcess *pty);
static void parseAheadBehind(QString const &s, Branch *b);
Git();
QString encodeQuotedText(QString const &str);
public:
Git(Context const &cx, QString const &repodir, QString const &submodpath, QString const &sshkey);
Git(Git &&r) = delete;
~Git() override;
using callback_t = bool (*)(void *, const char *, int);
void setLogCallback(callback_t func, void *cookie);
QByteArray toQByteArray() const;
void setGitCommand(QString const &gitcmd, const QString &sshcmd = {});
QString gitCommand() const;
void clearResult();
QString resultText() const;
bool chdirexec(std::function<bool ()> const &fn);
bool git(QString const &arg, bool chdir, bool errout = false, AbstractPtyProcess *pty = nullptr);
bool git(QString const &arg)
{
return git(arg, true);
}
void setWorkingRepositoryDir(QString const &repo, const QString &submodpath, const QString &sshkey);
QString workingDir() const;
QString const &sshKey() const;
void setSshKey(const QString &sshkey) const;
QString getCurrentBranchName();
bool isValidWorkingCopy() const;
QString version();
bool init();
QStringList getUntrackedFiles();
CommitItemList log_all(QString const &id, int maxcount);
CommitItemList log(int maxcount);
bool queryCommit(QString const &id, CommitItem *out);
struct CloneData {
QString url;
QString basedir;
QString subdir;
};
static CloneData preclone(QString const &url, QString const &path);
bool clone(CloneData const &data, AbstractPtyProcess *pty);
FileStatusList status_s();
bool cat_file(QString const &id, QByteArray *out);
void resetFile(QString const &path);
void resetAllFiles();
void removeFile(QString const &path);
void stage(QString const &path);
void stage(QStringList const &paths);
void unstage(QString const &path);
void unstage(QStringList const &paths);
void pull(AbstractPtyProcess *pty = nullptr);
void fetch(AbstractPtyProcess *pty = nullptr, bool prune = false);
void fetch_tags_f(AbstractPtyProcess *pty);
QList<Branch> branches();
int getProcessExitCode() const;
QString diff(QString const &old_id, QString const &new_id);
QString diff_file(QString const &old_path, QString const &new_path);
struct DiffRaw {
struct AB {
QString id;
QString mode;
} a, b;
QString state;
QStringList files;
};
struct Remote {
QString name;
QString url;
QString purpose;
QString ssh_key;
};
QList<DiffRaw> diff_raw(QString const &old_id, QString const &new_id);
static bool isValidID(QString const &id);
QString status();
bool commit(QString const &text, bool sign, AbstractPtyProcess *pty);
bool commit_amend_m(QString const &text, bool sign, AbstractPtyProcess *pty);
bool revert(QString const &id);
bool push(bool tags, AbstractPtyProcess *pty = nullptr);
void getRemoteURLs(QList<Remote> *out);
void createBranch(QString const &name);
void checkoutBranch(QString const &name);
- void mergeBranch(QString const &name, MergeFastForward ff);
+ void mergeBranch(QString const &name, MergeFastForward ff, bool squash);
void rebaseBranch(QString const &name);
static bool isValidWorkingCopy(QString const &dir);
QString diff_to_file(QString const &old_id, QString const &path);
QString errorMessage() const;
GitPtr dup() const;
QString rev_parse(QString const &name);
QList<Tag> tags();
QList<Tag> tags2();
bool tag(QString const &name, QString const &id = QString());
void delete_tag(QString const &name, bool remote);
void setRemoteURL(const Remote &remote);
void addRemoteURL(const Remote &remote);
void removeRemote(QString const &name);
QStringList getRemotes();
struct User {
QString name;
QString email;
};
enum class Source {
Default,
Global,
Local,
};
User getUser(Source purpose);
void setUser(User const&user, bool global);
bool reset_head1();
bool reset_hard();
bool clean_df();
void push_u(QString const &remote, QString const &branch, AbstractPtyProcess *pty);
QString objectType(QString const &id);
bool rm_cached(QString const &file);
void cherrypick(QString const &name);
QString getCherryPicking() const;
QString getMessage(const QString &id);
struct ReflogItem {
QString id;
QString head;
QString command;
QString message;
struct File {
QString atts_a;
QString atts_b;
QString id_a;
QString id_b;
QString type;
QString path;
};
QList<File> files;
};
using ReflogItemList = QList<ReflogItem>;
bool reflog(ReflogItemList *out, int maxcount = 100);
QByteArray blame(QString const &path);
enum SignPolicy {
Unset,
False,
True,
};
QString signingKey(Source purpose);
bool setSigningKey(QString const &id, bool global);
SignPolicy signPolicy(Source source);
bool setSignPolicy(Source source, SignPolicy policy);
bool configGpgProgram(QString const &path, bool global);
struct RemoteInfo {
QString commit_id;
QString name;
};
QList<RemoteInfo> ls_remote();
bool stash();
bool stash_apply();
bool stash_drop();
struct SubmoduleUpdateData {
bool init = true;
bool recursive = true;
};
QList<SubmoduleItem> submodules();
bool submodule_add(const CloneData &data, bool force, AbstractPtyProcess *pty);
bool submodule_update(const SubmoduleUpdateData &data, AbstractPtyProcess *pty);
};
void parseDiff(std::string const &s, Git::Diff const *info, Git::Diff *out);
void parseGitSubModules(QByteArray const &ba, QList<Git::SubmoduleItem> *out);
#endif // GIT_H
diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
index 233de05..a90ed0e 100644
--- a/src/MainWindow.cpp
+++ b/src/MainWindow.cpp
@@ -1,3472 +1,3473 @@
#include "MainWindow.h"
#include "ui_MainWindow.h"
#include "AboutDialog.h"
#include "ApplicationGlobal.h"
#include "AreYouSureYouWantToContinueConnectingDialog.h"
#include "BlameWindow.h"
#include "CheckoutDialog.h"
#include "CherryPickDialog.h"
#include "CloneFromGitHubDialog.h"
#include "CommitPropertyDialog.h"
#include "EditGitIgnoreDialog.h"
#include "EditTagsDialog.h"
#include "FindCommitDialog.h"
#include "GitDiff.h"
#include "JumpDialog.h"
#include "LineEditDialog.h"
#include "MergeDialog.h"
#include "MySettings.h"
#include "ObjectBrowserDialog.h"
#include "ReflogWindow.h"
#include "SetGpgSigningDialog.h"
#include "SettingsDialog.h"
#include "StatusLabel.h"
#include "SubmoduleUpdateDialog.h"
#include "SubmodulesDialog.h"
#include "TextEditDialog.h"
#include "UserEvent.h"
#include "SubmoduleMainWindow.h"
#include "FilePropertyDialog.h"
#include "common/misc.h"
#include <QClipboard>
#include <QDir>
#include <QElapsedTimer>
#include <QFileDialog>
#include <QFileIconProvider>
#include <QMessageBox>
#include <QMimeData>
#include <QPainter>
#include <QShortcut>
#include <QStandardPaths>
#include <QTimer>
#include "coloredit/ColorDialog.h"
struct EventItem {
QObject *receiver = nullptr;
QEvent *event = nullptr;
QDateTime at;
EventItem(QObject *receiver, QEvent *event, QDateTime const &at)
: receiver(receiver)
, event(event)
, at(at)
{
}
};
struct MainWindow::Private2 {
std::vector<EventItem> event_item_list;
bool is_online_mode = true;
QTimer interval_10ms_timer;
QImage graph_color;
QPixmap digits;
StatusLabel *status_bar_label;
QObject *last_focused_file_list = nullptr;
QListWidgetItem *last_selected_file_item = nullptr;
bool searching = false;
QString search_text;
int repos_panel_width = 0;
std::set<QString> ancestors;
QWidget *focused_widget = nullptr;
QList<int> splitter_h_sizes;
};
MainWindow::MainWindow(QWidget *parent)
: BasicMainWindow(parent)
, ui(new Ui::MainWindow)
, m1(new Private1)
, m2(new Private2)
{
ui->setupUi(this);
ui->frame_repository_wrapper->bind(this
, ui->tableWidget_log
, ui->listWidget_files
, ui->listWidget_unstaged
, ui->listWidget_staged
, ui->widget_diff_view
);
loadApplicationSettings();
m1->starting_dir = QDir::current().absolutePath();
{ // load graphic resources
QFileIconProvider icons;
m1->folder_icon = icons.icon(QFileIconProvider::Folder);
m1->repository_icon = QIcon(":/image/repository.png");
m1->signature_good_icon = QIcon(":/image/signature-good.png");
m1->signature_bad_icon = QIcon(":/image/signature-bad.png");
m1->signature_dubious_icon = QIcon(":/image/signature-dubious.png");
m1->transparent_pixmap = QPixmap(":/image/transparent.png");
}
#ifdef Q_OS_MACX
ui->action_about->setText("About Guitar...");
ui->action_edit_settings->setText("Settings...");
#endif
ui->splitter_v->setSizes({100, 400});
ui->splitter_h->setSizes({200, 100, 200});
m2->status_bar_label = new StatusLabel(this);
ui->statusBar->addWidget(m2->status_bar_label);
frame()->filediffwidget()->bind(this);
qApp->installEventFilter(this);
setShowLabels(appsettings()->show_labels, false);
ui->widget_log->setupForLogWidget(ui->verticalScrollBar_log, ui->horizontalScrollBar_log, themeForTextEditor());
onLogVisibilityChanged();
initNetworking();
showFileList(FilesListType::SingleList);
m2->digits.load(":/image/digits.png");
m2->graph_color = global->theme->graphColorMap();
frame()->prepareLogTableWidget();
#ifdef Q_OS_WIN
{
QFont font;
font = ui->label_repo_name->font();
font.setFamily("Meiryo");
ui->label_repo_name->setFont(font);
font = ui->label_branch_name->font();
font.setFamily("Meiryo");
ui->label_branch_name->setFont(font);
}
#endif
connect(this, &MainWindow::signalWriteLog, this, &MainWindow::writeLog_);
connect(ui->dockWidget_log, &QDockWidget::visibilityChanged, this, &MainWindow::onLogVisibilityChanged);
connect(ui->widget_log, &TextEditorWidget::idle, this, &MainWindow::onLogIdle);
connect(ui->treeWidget_repos, &RepositoriesTreeWidget::dropped, this, &MainWindow::onRepositoriesTreeDropped);
connect((AbstractPtyProcess *)getPtyProcess(), &AbstractPtyProcess::completed, this, &MainWindow::onPtyProcessCompleted);
connect(this, &MainWindow::remoteInfoChanged, [&](){
ui->lineEdit_remote->setText(currentRemoteName());
});
connect(this, &MainWindow::signalSetRemoteChanged, [&](bool f){
setRemoteChanged(f);
updateButton();
});
connect(new QShortcut(QKeySequence("Ctrl+T"), this), &QShortcut::activated, this, &MainWindow::test);
//
QString path = getBookmarksFilePath();
*getReposPtr() = RepositoryBookmark::load(path);
updateRepositoriesList();
{
// アイコン取得機能
webContext()->set_keep_alive_enabled(true);
getAvatarLoader()->start(this);
connect(getAvatarLoader(), &AvatarLoader::updated, this, &MainWindow::onAvatarUpdated);
}
connect(frame()->filediffwidget(), &FileDiffWidget::textcodecChanged, [&](){ updateDiffView(frame()); });
if (!global->start_with_shift_key && appsettings()->remember_and_restore_window_position) {
Qt::WindowStates state = windowState();
MySettings settings;
settings.beginGroup("MainWindow");
bool maximized = settings.value("Maximized").toBool();
restoreGeometry(settings.value("Geometry").toByteArray());
settings.endGroup();
if (maximized) {
state |= Qt::WindowMaximized;
setWindowState(state);
}
}
ui->action_sidebar->setChecked(true);
startTimers();
}
MainWindow::~MainWindow()
{
cancelPendingUserEvents();
stopPtyProcess();
getAvatarLoader()->stop();
deleteTempFiles();
delete m2;
delete m1;
delete ui;
}
RepositoryWrapperFrame *MainWindow::frame()
{
return ui->frame_repository_wrapper;
}
RepositoryWrapperFrame const *MainWindow::frame() const
{
return ui->frame_repository_wrapper;
}
/**
* @brief イベントをポストする
* @param receiver 宛先
* @param event イベント
* @param ms_later 遅延時間(0なら即座)
*/
void MainWindow::postEvent(QObject *receiver, QEvent *event, int ms_later)
{
if (ms_later <= 0) {
QApplication::postEvent(this, event);
} else {
auto at = QDateTime::currentDateTime().addMSecs(ms_later);
m2->event_item_list.emplace_back(receiver, event, at);
std::stable_sort(m2->event_item_list.begin(), m2->event_item_list.end(), [](EventItem const &l, EventItem const &r){
return l.at > r.at; // 降順
});
}
}
/**
* @brief ユーザー関数イベントをポストする
* @param fn 関数
* @param v QVariant
* @param p ポインタ
* @param ms_later 遅延時間(0なら即座)
*/
void MainWindow::postUserFunctionEvent(const std::function<void (const QVariant &, void *ptr)> &fn, const QVariant &v, void *p, int ms_later)
{
postEvent(this, new UserFunctionEvent(fn, v, p), ms_later);
}
/**
* @brief 未送信のイベントをすべて削除する
*/
void MainWindow::cancelPendingUserEvents()
{
for (auto &item : m2->event_item_list) {
delete item.event;
}
m2->event_item_list.clear();
}
/**
* @brief 開始イベントをポストする
*/
void MainWindow::postStartEvent(int ms_later)
{
postEvent(this, new StartEvent, ms_later);
}
/**
* @brief インターバルタイマを開始する
*/
void MainWindow::startTimers()
{
// タイマ開始
connect(&m2->interval_10ms_timer, &QTimer::timeout, this, &MainWindow::onInterval10ms);
m2->interval_10ms_timer.setInterval(10);
m2->interval_10ms_timer.start();
}
/**
* @brief 10ms間隔のインターバルタイマ
*/
void MainWindow::onInterval10ms()
{
{
// ユーザーイベントの処理
std::vector<EventItem> items; // 処理するイベント
QDateTime now = QDateTime::currentDateTime(); // 現在時刻
size_t i = m2->event_item_list.size(); // 後ろから走査
while (i > 0) {
i--;
if (m2->event_item_list[i].at <= now) { // 予約時間を過ぎていたら
items.push_back(m2->event_item_list[i]); // 処理リストに追加
m2->event_item_list.erase(m2->event_item_list.begin() + i); // 処理待ちリストから削除
}
}
// イベントをポストする
for (auto it = items.rbegin(); it != items.rend(); it++) {
QApplication::postEvent(it->receiver, it->event);
}
}
{
// PTYプロセスの監視
bool running = getPtyProcess()->isRunning();
if (ui->toolButton_stop_process->isEnabled() != running) {
ui->toolButton_stop_process->setEnabled(running); // ボタンの状態を設定
ui->action_stop_process->setEnabled(running);
setNetworkingCommandsEnabled(!running);
}
if (!running) {
setInteractionMode(InteractionMode::None);
}
// PTYプロセスの出力をログに書き込む
while (1) {
char tmp[1024];
int len = getPtyProcess()->readOutput(tmp, sizeof(tmp));
if (len < 1) break;
writeLog(tmp, len);
}
}
}
bool MainWindow::shown()
{
m2->repos_panel_width = ui->stackedWidget_leftpanel->width();
ui->stackedWidget_leftpanel->setCurrentWidget(ui->page_repos);
ui->action_repositories_panel->setChecked(true);
{
MySettings settings;
{
settings.beginGroup("Remote");
bool f = settings.value("Online", true).toBool();
settings.endGroup();
setRemoteOnline(f, false);
}
{
settings.beginGroup("MainWindow");
int n = settings.value("FirstColumnWidth", 50).toInt();
if (n < 10) n = 50;
frame()->logtablewidget()->setColumnWidth(0, n);
settings.endGroup();
}
}
updateUI();
postStartEvent(100); // 開始イベント(100ms後)
return true;
}
bool MainWindow::isUninitialized()
{
return !misc::isExecutable(appsettings()->git_command) || !misc::isExecutable(appsettings()->file_command);
}
void MainWindow::onStartEvent()
{
if (isUninitialized()) { // gitコマンドの有効性チェック
if (!execWelcomeWizardDialog()) { // ようこそダイアログを表示
close(); // キャンセルされたらプログラム終了
}
}
if (isUninitialized()) { // 正しく初期設定されたか
postStartEvent(100); // 初期設定されなかったら、もういちどようこそダイアログを出す(100ms後)
} else {
// 外部コマンド登録
setGitCommand(appsettings()->git_command, false);
setFileCommand(appsettings()->file_command, false);
setGpgCommand(appsettings()->gpg_command, false);
setSshCommand(appsettings()->ssh_command, false);
// メインウィンドウのタイトルを設定
updateWindowTitle(git());
// プログラムバーション表示
writeLog(AboutDialog::appVersion() + '\n');
// gitコマンドバージョン表示
logGitVersion();
}
}
void MainWindow::setCurrentLogRow(RepositoryWrapperFrame *frame, int row)
{
if (row >= 0 && row < frame->logtablewidget()->rowCount()) {
updateStatusBarText(frame);
frame->logtablewidget()->setFocus();
frame->logtablewidget()->setCurrentCell(row, 2);
}
}
bool MainWindow::eventFilter(QObject *watched, QEvent *event)
{
QEvent::Type et = event->type();
if (et == QEvent::KeyPress) {
if (QApplication::activeModalWidget()) {
// thru
} else {
auto *e = dynamic_cast<QKeyEvent *>(event);
Q_ASSERT(e);
int k = e->key();
if (k == Qt::Key_Escape) {
if (centralWidget()->isAncestorOf(qApp->focusWidget())) {
ui->treeWidget_repos->setFocus();
return true;
}
}
if (e->modifiers() & Qt::ControlModifier) {
if (k == Qt::Key_Up || k == Qt::Key_Down) {
int rows = frame()->logtablewidget()->rowCount();
int row = frame()->logtablewidget()->currentRow();
if (k == Qt::Key_Up) {
if (row > 0) {
row--;
}
} else if (k == Qt::Key_Down) {
if (row + 1 < rows) {
row++;
}
}
frame()->logtablewidget()->setCurrentCell(row, 0);
return true;
}
}
if (watched == ui->treeWidget_repos) {
if (k == Qt::Key_Enter || k == Qt::Key_Return) {
openSelectedRepository();
return true;
}
if (!(e->modifiers() & Qt::ControlModifier)) {
if (k >= 0 && k < 128 && QChar((uchar)k).isLetterOrNumber()) {
appendCharToRepoFilter(k);
return true;
}
if (k == Qt::Key_Backspace) {
backspaceRepoFilter();
return true;
}
if (k == Qt::Key_Escape) {
clearRepoFilter();
return true;
}
}
} else if (watched == frame()->logtablewidget()) {
if (k == Qt::Key_Home) {
setCurrentLogRow(frame(), 0);
return true;
}
if (k == Qt::Key_Escape) {
ui->treeWidget_repos->setFocus();
return true;
}
} else if (watched == frame()->fileslistwidget() || watched == frame()->unstagedFileslistwidget() || watched == frame()->stagedFileslistwidget()) {
if (k == Qt::Key_Escape) {
frame()->logtablewidget()->setFocus();
return true;
}
}
}
} else if (et == QEvent::FocusIn) {
auto SelectItem = [](QListWidget *w){
int row = w->currentRow();
if (row < 0) {
row = 0;
w->setCurrentRow(row);
}
w->setItemSelected(w->item(row), true);
w->viewport()->update();
};
// ファイルリストがフォーカスを得たとき、diffビューを更新する。(コンテキストメニュー対応)
if (watched == frame()->unstagedFileslistwidget()) {
m2->last_focused_file_list = watched;
updateStatusBarText(frame());
updateUnstagedFileCurrentItem(frame());
SelectItem(frame()->unstagedFileslistwidget());
return true;
}
if (watched == frame()->stagedFileslistwidget()) {
m2->last_focused_file_list = watched;
updateStatusBarText(frame());
updateStagedFileCurrentItem(frame());
SelectItem(frame()->stagedFileslistwidget());
return true;
}
if (watched == frame()->fileslistwidget()) {
m2->last_focused_file_list = watched;
SelectItem(frame()->fileslistwidget());
return true;
}
}
return false;
}
bool MainWindow::event(QEvent *event)
{
QEvent::Type et = event->type();
if (et == QEvent::KeyPress) {
auto *e = dynamic_cast<QKeyEvent *>(event);
Q_ASSERT(e);
int k = e->key();
if (k == Qt::Key_Escape) {
emit onEscapeKeyPressed();
} else if (k == Qt::Key_Delete) {
if (qApp->focusWidget() == ui->treeWidget_repos) {
removeSelectedRepositoryFromBookmark(true);
return true;
}
}
} else if (et == (QEvent::Type)UserEvent::UserFunction) {
if (auto *e = (UserFunctionEvent *)event) {
e->func(e->var, e->ptr);
return true;
}
}
return BasicMainWindow::event(event);
}
void MainWindow::customEvent(QEvent *e)
{
if (e->type() == (QEvent::Type)UserEvent::Start) {
onStartEvent();
return;
}
}
void MainWindow::closeEvent(QCloseEvent *event)
{
MySettings settings;
if (appsettings()->remember_and_restore_window_position) {
setWindowOpacity(0);
Qt::WindowStates state = windowState();
bool maximized = (state & Qt::WindowMaximized) != 0;
if (maximized) {
state &= ~Qt::WindowMaximized;
setWindowState(state);
}
{
settings.beginGroup("MainWindow");
settings.setValue("Maximized", maximized);
settings.setValue("Geometry", saveGeometry());
settings.endGroup();
}
}
{
settings.beginGroup("MainWindow");
settings.setValue("FirstColumnWidth", frame()->logtablewidget()->columnWidth(0));
settings.endGroup();
}
QMainWindow::closeEvent(event);
}
void MainWindow::setStatusBarText(QString const &text)
{
m2->status_bar_label->setText(text);
}
void MainWindow::clearStatusBarText()
{
setStatusBarText(QString());
}
void MainWindow::onLogVisibilityChanged()
{
ui->action_window_log->setChecked(ui->dockWidget_log->isVisible());
}
void MainWindow::internalWriteLog(char const *ptr, int len)
{
ui->widget_log->logicalMoveToBottom();
ui->widget_log->write(ptr, len, false);
ui->widget_log->setChanged(false);
setInteractionCanceled(false);
}
void MainWindow::buildRepoTree(QString const &group, QTreeWidgetItem *item, QList<RepositoryItem> *repos)
{
QString name = item->text(0);
if (isGroupItem(item)) {
int n = item->childCount();
for (int i = 0; i < n; i++) {
QTreeWidgetItem *child = item->child(i);
QString sub = group / name;
buildRepoTree(sub, child, repos);
}
} else {
RepositoryItem const *repo = repositoryItem(item);
if (repo) {
RepositoryItem newrepo = *repo;
newrepo.name = name;
newrepo.group = group;
item->setData(0, IndexRole, repos->size());
repos->push_back(newrepo);
}
}
}
void MainWindow::refrectRepositories()
{
QList<RepositoryItem> newrepos;
int n = ui->treeWidget_repos->topLevelItemCount();
for (int i = 0; i < n; i++) {
QTreeWidgetItem *item = ui->treeWidget_repos->topLevelItem(i);
buildRepoTree(QString(), item, &newrepos);
}
*getReposPtr() = std::move(newrepos);
saveRepositoryBookmarks();
}
void MainWindow::onRepositoriesTreeDropped()
{
refrectRepositories();
QTreeWidgetItem *item = ui->treeWidget_repos->currentItem();
if (item) item->setExpanded(true);
}
const QPixmap &MainWindow::digitsPixmap() const
{
return m2->digits;
}
int MainWindow::digitWidth() const
{
return 5;
}
int MainWindow::digitHeight() const
{
return 7;
}
void MainWindow::drawDigit(QPainter *pr, int x, int y, int n) const
{
int w = digitWidth();
int h = digitHeight();
pr->drawPixmap(x, y, w, h, m2->digits, n * w, 0, w, h);
}
QString MainWindow::defaultWorkingDir() const
{
return appsettings()->default_working_dir;
}
/**
* @brief サブモジュール情報を取得する
* @param path
* @param commit コミット情報を取得(nullptr可)
* @return
*/
Git::SubmoduleItem const *MainWindow::querySubmoduleByPath(const QString &path, Git::CommitItem *commit)
{
if (commit) *commit = {};
for (auto const &submod : m1->submodules) {
if (submod.path == path) {
if (commit) {
GitPtr g = git(submod);
g->queryCommit(submod.id, commit);
}
return &submod;
}
}
return nullptr;
}
QColor MainWindow::color(unsigned int i)
{
unsigned int n = m2->graph_color.width();
if (n > 0) {
n--;
if (i > n) i = n;
QRgb const *p = (QRgb const *)m2->graph_color.scanLine(0);
return QColor(qRed(p[i]), qGreen(p[i]), qBlue(p[i]));
}
return Qt::black;
}
//QString MainWindow::currentWorkingCopyDir() const
//{
// QString workdir = BasicMainWindow::currentWorkingCopyDir();
// if (workdir.isEmpty()) {
// RepositoryItem const *repo = selectedRepositoryItem();
// if (repo) {
// workdir = repo->local_dir;
// return workdir;
// }
// }
// return workdir;
//}
RepositoryItem const *MainWindow::findRegisteredRepository(QString *workdir) const
{
*workdir = QDir(*workdir).absolutePath();
workdir->replace('\\', '/');
if (Git::isValidWorkingCopy(*workdir)) {
for (RepositoryItem const &item : getRepos()) {
Qt::CaseSensitivity cs = Qt::CaseSensitive;
#ifdef Q_OS_WIN
cs = Qt::CaseInsensitive;
#endif
if (workdir->compare(item.local_dir, cs) == 0) {
return &item;
}
}
}
return nullptr;
}
int MainWindow::repositoryIndex_(QTreeWidgetItem const *item) const
{
if (item) {
int i = item->data(0, IndexRole).toInt();
if (i >= 0 && i < getRepos().size()) {
return i;
}
}
return -1;
}
RepositoryItem const *MainWindow::repositoryItem(QTreeWidgetItem const *item) const
{
int row = repositoryIndex_(item);
QList<RepositoryItem> const &repos = getRepos();
return (row >= 0 && row < repos.size()) ? &repos[row] : nullptr;
}
RepositoryItem const *MainWindow::selectedRepositoryItem() const
{
return repositoryItem(ui->treeWidget_repos->currentItem());
}
static QTreeWidgetItem *newQTreeWidgetItem()
{
auto *item = new QTreeWidgetItem;
item->setSizeHint(0, QSize(20, 20));
return item;
}
QTreeWidgetItem *MainWindow::newQTreeWidgetFolderItem(QString const &name)
{
QTreeWidgetItem *item = newQTreeWidgetItem();
item->setText(0, name);
item->setData(0, IndexRole, GroupItem);
item->setIcon(0, getFolderIcon());
item->setFlags(item->flags() | Qt::ItemIsEditable);
return item;
}
void MainWindow::updateRepositoriesList()
{
QString path = getBookmarksFilePath();
auto *repos = getReposPtr();
*repos = RepositoryBookmark::load(path);
QString filter = getRepositoryFilterText();
ui->treeWidget_repos->clear();
std::map<QString, QTreeWidgetItem *> parentmap;
for (int i = 0; i < repos->size(); i++) {
RepositoryItem const &repo = repos->at(i);
if (!filter.isEmpty() && repo.name.indexOf(filter, 0, Qt::CaseInsensitive) < 0) {
continue;
}
QTreeWidgetItem *parent = nullptr;
{
QString group = repo.group;
if (group.startsWith('/')) {
group = group.mid(1);
}
auto it = parentmap.find(group);
if (it != parentmap.end()) {
parent = it->second;
}
if (!parent) {
QStringList list = group.split('/', QString::SkipEmptyParts);
if (list.isEmpty()) {
list.push_back(tr("Default"));
}
for (QString const &name : list) {
if (name.isEmpty()) continue;
if (!parent) {
auto it = parentmap.find(name);
if (it != parentmap.end()) {
parent = it->second;
} else {
parent = newQTreeWidgetFolderItem(name);
ui->treeWidget_repos->addTopLevelItem(parent);
}
} else {
QTreeWidgetItem *child = newQTreeWidgetFolderItem(name);
parent->addChild(child);
parent = child;
}
parent->setExpanded(true);
}
Q_ASSERT(parent);
parentmap[group] = parent;
}
parent->setData(0, FilePathRole, "");
}
QTreeWidgetItem *child = newQTreeWidgetItem();
child->setText(0, repo.name);
child->setData(0, IndexRole, i);
child->setIcon(0, getRepositoryIcon());
child->setFlags(child->flags() & ~Qt::ItemIsDropEnabled);
parent->addChild(child);
parent->setExpanded(true);
}
}
void MainWindow::showFileList(FilesListType files_list_type)
{
switch (files_list_type) {
case FilesListType::SingleList:
ui->stackedWidget_filelist->setCurrentWidget(ui->page_files);
break;
case FilesListType::SideBySide:
ui->stackedWidget_filelist->setCurrentWidget(ui->page_uncommited);
break;
}
}
/**
* @brief ファイルリストを消去
* @param frame
*/
void MainWindow::clearFileList(RepositoryWrapperFrame *frame)
{
showFileList(FilesListType::SingleList);
frame->unstagedFileslistwidget()->clear();
frame->stagedFileslistwidget()->clear();
frame->fileslistwidget()->clear();
}
void MainWindow::clearDiffView(RepositoryWrapperFrame *frame)
{
frame->filediffwidget()->clearDiffView();
}
void MainWindow::clearRepositoryInfo()
{
internalClearRepositoryInfo();
ui->label_repo_name->setText(QString());
ui->label_branch_name->setText(QString());
}
void MainWindow::setRepositoryInfo(QString const &reponame, QString const &brname)
{
ui->label_repo_name->setText(reponame);
ui->label_branch_name->setText(brname);
}
/**
* @brief 指定のコミットにおけるサブモジュールリストを取得
* @param g
* @param id
* @param out
*/
void MainWindow::updateSubmodules(GitPtr g, QString const &id, QList<Git::SubmoduleItem> *out)
{
*out = {};
QList<Git::SubmoduleItem> submodules;
if (id.isEmpty()) {
submodules = g->submodules();
} else {
GitTreeItemList list;
GitObjectCache objcache;
objcache.setup(g);
// サブモジュールリストを取得する
{
GitCommit tree;
GitCommit::parseCommit(&objcache, id, &tree);
parseGitTreeObject(&objcache, tree.tree_id, {}, &list);
for (GitTreeItem const &item : list) {
if (item.type == GitTreeItem::Type::BLOB && item.name == ".gitmodules") {
Git::Object obj = objcache.catFile(item.id);
if (!obj.content.isEmpty()) {
parseGitSubModules(obj.content, &submodules);
}
}
}
}
// サブモジュールに対応するIDを求める
for (int i = 0; i < submodules.size(); i++) {
QStringList vars = submodules[i].path.split('/');
for (int j = 0; j < vars.size(); j++) {
for (int k = 0; k < list.size(); k++) {
if (list[k].name == vars[j]) {
if (list[k].type == GitTreeItem::Type::BLOB) {
if (j + 1 == vars.size()) {
submodules[i].id = list[k].id;
goto done;
}
} else if (list[k].type == GitTreeItem::Type::TREE) {
Git::Object obj = objcache.catFile(list[k].id);
parseGitTreeObject(obj.content, {}, &list);
break;
}
}
}
}
done:;
}
}
*out = submodules;
}
const Git::CommitItemList &MainWindow::getLogs(RepositoryWrapperFrame const *frame) const
{
// return m1->logs;
return frame->logs;
}
const Git::CommitItem *MainWindow::getLog(RepositoryWrapperFrame const *frame, int index) const
{
Git::CommitItemList const &logs = frame->logs;
return (index >= 0 && index < (int)logs.size()) ? &logs[index] : nullptr;
}
Git::CommitItemList *MainWindow::getLogsPtr(RepositoryWrapperFrame *frame)
{
return &frame->logs;
}
void MainWindow::setLogs(RepositoryWrapperFrame *frame, const Git::CommitItemList &logs)
{
frame->logs = logs;
}
void MainWindow::clearLogs(RepositoryWrapperFrame *frame)
{
frame->logs.clear();
}
/**
* @brief リストウィジェット用ファイルアイテムを作成する
* @param data
* @return
*/
QListWidgetItem *MainWindow::NewListWidgetFileItem(MainWindow::ObjectData const &data)
{
const bool issubmodule = data.submod; // サブモジュール
QString header = data.header; // ヘッダ(バッジ識別子)
if (header.isEmpty()) {
header = "(??\?) "; // damn trigraph
}
QString text = data.path; // テキスト
if (issubmodule) {
text += QString(" <%0> [%1] %2")
.arg(data.submod.id.mid(0, 7))
.arg(misc::makeDateTimeString(data.submod_commit.commit_date))
.arg(data.submod_commit.message)
;
}
QListWidgetItem *item = new QListWidgetItem(text);
item->setSizeHint(QSize(item->sizeHint().width(), 18));
item->setData(FilePathRole, data.path);
item->setData(DiffIndexRole, data.idiff);
item->setData(ObjectIdRole, data.id);
// item->setData(HunkIndexRole, -1);
item->setData(HeaderRole, header);
item->setData(SubmodulePathRole, data.submod.path);
if (issubmodule) {
item->setToolTip(text); // ツールチップ
}
return item;
}
/**
* @brief 差分リスト情報をもとにリストウィジェットへアイテムを追加する
* @param diff_list
* @param fn_add_item
*/
void MainWindow::addDiffItems(const QList<Git::Diff> *diff_list, const std::function<void (ObjectData const &data)> &fn_add_item)
{
for (int idiff = 0; idiff < diff_list->size(); idiff++) {
Git::Diff const &diff = diff_list->at(idiff);
QString header;
switch (diff.type) {
case Git::Diff::Type::Modify: header = "(chg) "; break;
case Git::Diff::Type::Copy: header = "(cpy) "; break;
case Git::Diff::Type::Rename: header = "(ren) "; break;
case Git::Diff::Type::Create: header = "(add) "; break;
case Git::Diff::Type::Delete: header = "(del) "; break;
case Git::Diff::Type::ChType: header = "(chg) "; break;
case Git::Diff::Type::Unmerged: header = "(unmerged) "; break;
}
ObjectData data;
data.id = diff.blob.b_id;
data.path = diff.path;
data.submod = diff.b_submodule.item;
data.submod_commit = diff.b_submodule.commit;
data.header = header;
data.idiff = idiff;
fn_add_item(data);
}
}
/**
* @brief コミットログを更新(100ms遅延)
*/
void MainWindow::updateCommitLogTableLater(RepositoryWrapperFrame *frame, int ms_later)
{
postUserFunctionEvent([&](QVariant const &, void *ptr){
qDebug() << (void *)ptr;
if (ptr) {
RepositoryWrapperFrame *frame = reinterpret_cast<RepositoryWrapperFrame *>(ptr);
frame->logtablewidget()->viewport()->update();
}
}, {}, reinterpret_cast<void *>(frame), ms_later);
}
QString MainWindow::getObjectID(QListWidgetItem *item)
{
if (!item) return {};
return item->data(ObjectIdRole).toString();
}
QString MainWindow::getFilePath(QListWidgetItem *item)
{
if (!item) return {};
return item->data(FilePathRole).toString();
}
QString MainWindow::getSubmodulePath(QListWidgetItem *item)
{
if (!item) return {};
return item->data(SubmodulePathRole).toString();
}
/**
* @brief ファイルリストを更新
* @param id コミットID
* @param wait
*/
void MainWindow::updateFilesList(RepositoryWrapperFrame *frame, QString id, bool wait)
{
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
if (!wait) return;
clearFileList(frame);
Git::FileStatusList stats = g->status_s();
setUncommitedChanges(!stats.empty());
FilesListType files_list_type = FilesListType::SingleList;
bool staged = false;
auto AddItem = [&](ObjectData const &data){
QListWidgetItem *item = NewListWidgetFileItem(data);
switch (files_list_type) {
case FilesListType::SingleList:
frame->fileslistwidget()->addItem(item);
break;
case FilesListType::SideBySide:
if (staged) {
frame->stagedFileslistwidget()->addItem(item);
} else {
frame->unstagedFileslistwidget()->addItem(item);
}
break;
}
};
if (id.isEmpty()) {
bool uncommited = isThereUncommitedChanges();
if (uncommited) {
files_list_type = FilesListType::SideBySide;
}
bool ok = false;
auto diffs = makeDiffs(frame, uncommited ? QString() : id, &ok);
setDiffResult(diffs);
if (!ok) return;
std::map<QString, int> diffmap;
for (int idiff = 0; idiff < diffResult()->size(); idiff++) {
Git::Diff const &diff = diffResult()->at(idiff);
QString filename = diff.path;
if (!filename.isEmpty()) {
diffmap[filename] = idiff;
}
}
showFileList(files_list_type);
for (Git::FileStatus const &s : stats) {
staged = (s.isStaged() && s.code_y() == ' ');
int idiff = -1;
QString header;
auto it = diffmap.find(s.path1());
Git::Diff const *diff = nullptr;
if (it != diffmap.end()) {
idiff = it->second;
diff = &diffResult()->at(idiff);
}
QString path = s.path1();
if (s.code() == Git::FileStatusCode::Unknown) {
qDebug() << "something wrong...";
} else if (s.code() == Git::FileStatusCode::Untracked) {
// nop
} else if (s.isUnmerged()) {
header += "(unmerged) ";
} else if (s.code() == Git::FileStatusCode::AddedToIndex) {
header = "(add) ";
} else if (s.code_x() == 'D' || s.code_y() == 'D' || s.code() == Git::FileStatusCode::DeletedFromIndex) {
header = "(del) ";
} else if (s.code_x() == 'R' || s.code() == Git::FileStatusCode::RenamedInIndex) {
header = "(ren) ";
path = s.path2(); // renamed newer path
} else if (s.code_x() == 'M' || s.code_y() == 'M') {
header = "(chg) ";
}
ObjectData data;
data.path = path;
data.header = header;
data.idiff = idiff;
if (diff) {
data.submod = diff->b_submodule.item; // TODO:
if (data.submod) {
GitPtr g = git(data.submod);
g->queryCommit(data.submod.id, &data.submod_commit);
}
}
AddItem(data);
}
} else {
bool ok = false;
auto diffs = makeDiffs(frame, id, &ok);
setDiffResult(diffs);
if (!ok) return;
showFileList(files_list_type);
addDiffItems(diffResult(), AddItem);
}
for (Git::Diff const &diff : *diffResult()) {
QString key = GitDiff::makeKey(diff);
(*getDiffCacheMap(frame))[key] = diff;
}
}
/**
* @brief ファイルリストを更新
* @param id
* @param diff_list
* @param listwidget
*/
void MainWindow::updateFilesList2(RepositoryWrapperFrame *frame, QString const &id, QList<Git::Diff> *diff_list, QListWidget *listwidget)
{
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
listwidget->clear();
auto AddItem = [&](ObjectData const &data){
QListWidgetItem *item = NewListWidgetFileItem(data);
listwidget->addItem(item);
};
GitDiff dm(getObjCache(frame));
if (!dm.diff(id, submodules(), diff_list)) return;
addDiffItems(diff_list, AddItem);
}
void MainWindow::updateFilesList(RepositoryWrapperFrame *frame, Git::CommitItem const &commit, bool wait)
{
QString id;
if (Git::isUncommited(commit)) {
// empty id for uncommited changes
} else {
id = commit.commit_id;
}
updateFilesList(frame, id, wait);
}
void MainWindow::updateCurrentFilesList(RepositoryWrapperFrame *frame)
{
auto logs = getLogs(frame);
QTableWidgetItem *item = frame->logtablewidget()->item(selectedLogIndex(frame), 0);
if (!item) return;
int index = item->data(IndexRole).toInt();
int count = (int)logs.size();
if (index < count) {
updateFilesList(frame, logs[index], true);
}
}
//void MainWindow::prepareLogTableWidget(RepositoryWrapperFrame *frame)
//{
// frame->prepareLogTableWidget();
//}
void MainWindow::detectGitServerType(GitPtr const &g)
{
setServerType(ServerType::Standard);
*ptrGitHub() = GitHubRepositoryInfo();
QString push_url;
QList<Git::Remote> remotes;
g->getRemoteURLs(&remotes);
for (Git::Remote const &r : remotes) {
if (r.purpose == "push") {
push_url = r.url;
}
}
auto Check = [&](QString const &s){
int i = push_url.indexOf(s);
if (i > 0) return i + s.size();
return 0;
};
// check GitHub
int pos = Check("@github.com:");
if (pos == 0) {
pos = Check("://github.com/");
}
if (pos > 0) {
int end = push_url.size();
{
QString s = ".git";
if (push_url.endsWith(s)) {
end -= s.size();
}
}
QString s = push_url.mid(pos, end - pos);
int i = s.indexOf('/');
if (i > 0) {
auto *p = ptrGitHub();
QString user = s.mid(0, i);
QString repo = s.mid(i + 1);
p->owner_account_name = user;
p->repository_name = repo;
}
setServerType(ServerType::GitHub);
}
}
void MainWindow::clearLog(RepositoryWrapperFrame *frame)
{
clearLogs(frame);
clearLabelMap(frame);
setUncommitedChanges(false);
frame->clearLogContents();
}
void MainWindow::openRepository_(GitPtr g, bool keep_selection)
{
openRepository_(frame(), g, keep_selection);
}
void MainWindow::openRepository_(RepositoryWrapperFrame *frame, GitPtr g, bool keep_selection)
{
getObjCache(frame)->setup(g);
int scroll_pos = -1;
int select_row = -1;
if (keep_selection) {
scroll_pos = frame->logtablewidget()->verticalScrollBar()->value();
select_row = frame->logtablewidget()->currentRow();
}
if (isValidWorkingCopy(g), 1) { ///
bool do_fetch = isOnlineMode() && (getForceFetch() || appsettings()->automatically_fetch_when_opening_the_repository);
setForceFetch(false);
if (do_fetch) {
if (!fetch(g, false)) {
return;
}
}
clearLog(frame);
clearRepositoryInfo();
detectGitServerType(g);
updateFilesList(frame, QString(), true);
bool canceled = false;
frame->logtablewidget()->setEnabled(false);
// ログを取得
setLogs(frame, retrieveCommitLog(g));
// ブランチを取得
queryBranches(frame, g);
// タグを取得
ptrTagMap(frame)->clear();
QList<Git::Tag> tags = g->tags();
for (Git::Tag const &tag : tags) {
Git::Tag t = tag;
t.id = getObjCache(frame)->getCommitIdFromTag(t.id);
(*ptrTagMap(frame))[t.id].push_back(t);
}
frame->logtablewidget()->setEnabled(true);
updateCommitLogTableLater(frame, 100); // ミコットログを更新(100ms後)
if (canceled) return;
QString branch_name;
if (currentBranch().flags & Git::Branch::HeadDetachedAt) {
branch_name += QString("(HEAD detached at %1)").arg(currentBranchName());
}
if (currentBranch().flags & Git::Branch::HeadDetachedFrom) {
branch_name += QString("(HEAD detached from %1)").arg(currentBranchName());
}
if (branch_name.isEmpty()) {
branch_name = currentBranchName();
}
QString repo_name = currentRepositoryName();
setRepositoryInfo(repo_name, branch_name);
} else {
clearLog(frame);
clearRepositoryInfo();
}
if (!g) return;
updateRemoteInfo();
updateWindowTitle(g);
setHeadId(getObjCache(frame)->revParse("HEAD"));
if (isThereUncommitedChanges()) {
Git::CommitItem item;
item.parent_ids.push_back(currentBranch().id);
item.message = tr("Uncommited changes");
auto p = getLogsPtr(frame);
p->insert(p->begin(), item);
}
frame->prepareLogTableWidget();
auto const &logs = getLogs(frame);
const int count = logs.size();
frame->logtablewidget()->setRowCount(count);
int selrow = 0;
for (int row = 0; row < count; row++) {
Git::CommitItem const *commit = &logs[row];
{
auto *item = new QTableWidgetItem;
item->setData(IndexRole, row);
frame->logtablewidget()->setItem(row, 0, item);
}
int col = 1; // カラム0はコミットグラフなので、その次から
auto AddColumn = [&](QString const &text, bool bold, QString const &tooltip){
auto *item = new QTableWidgetItem(text);
if (!tooltip.isEmpty()) {
QString tt = tooltip;
tt.replace('\n', ' ');
tt = tt.toHtmlEscaped();
tt = "<p style='white-space: pre'>" + tt + "</p>";
item->setToolTip(tt);
}
if (bold) {
QFont font = item->font();
font.setBold(true);
item->setFont(font);
}
frame->logtablewidget()->setItem(row, col, item);
col++;
};
QString commit_id;
QString datetime;
QString author;
QString message;
QString message_ex;
bool isHEAD = (commit->commit_id == getHeadId());
bool bold = false;
{
if (Git::isUncommited(*commit)) { // 未コミットの時
bold = true; // 太字
selrow = row;
} else {
if (isHEAD && !isThereUncommitedChanges()) { // HEADで、未コミットがないとき
bold = true; // 太字
selrow = row;
}
commit_id = abbrevCommitID(*commit);
}
datetime = misc::makeDateTimeString(commit->commit_date);
author = commit->author;
message = commit->message;
message_ex = makeCommitInfoText(frame, row, &(*getLabelMap(frame))[row]);
}
AddColumn(commit_id, false, QString());
AddColumn(datetime, false, QString());
AddColumn(author, false, QString());
AddColumn(message, bold, message + message_ex);
frame->logtablewidget()->setRowHeight(row, 24);
}
int t = frame->logtablewidget()->columnWidth(0);
frame->logtablewidget()->resizeColumnsToContents();
frame->logtablewidget()->setColumnWidth(0, t);
frame->logtablewidget()->horizontalHeader()->setStretchLastSection(false);
frame->logtablewidget()->horizontalHeader()->setStretchLastSection(true);
m2->last_focused_file_list = nullptr;
frame->logtablewidget()->setFocus();
if (select_row < 0) {
setCurrentLogRow(frame, selrow);
} else {
setCurrentLogRow(frame, select_row);
frame->logtablewidget()->verticalScrollBar()->setValue(scroll_pos >= 0 ? scroll_pos : 0);
}
updateUI();
}
void MainWindow::removeSelectedRepositoryFromBookmark(bool ask)
{
int i = indexOfRepository(ui->treeWidget_repos->currentItem());
removeRepositoryFromBookmark(i, ask);
}
void MainWindow::setNetworkingCommandsEnabled(bool enabled)
{
ui->action_clone->setEnabled(enabled);
ui->toolButton_clone->setEnabled(enabled);
if (!Git::isValidWorkingCopy(currentWorkingCopyDir())) {
enabled = false;
}
bool opened = !currentRepository().name.isEmpty();
ui->action_fetch->setEnabled(enabled || opened);
ui->toolButton_fetch->setEnabled(enabled || opened);
if (isOnlineMode()) {
ui->action_fetch->setText(tr("Fetch"));
ui->toolButton_fetch->setText(tr("Fetch"));
} else {
ui->action_fetch->setText(tr("Update"));
ui->toolButton_fetch->setText(tr("Update"));
}
ui->action_fetch_prune->setEnabled(enabled);
ui->action_pull->setEnabled(enabled);
ui->action_push->setEnabled(enabled);
ui->action_push_u->setEnabled(enabled);
ui->action_push_all_tags->setEnabled(enabled);
ui->toolButton_pull->setEnabled(enabled);
ui->toolButton_push->setEnabled(enabled);
}
void MainWindow::updateUI()
{
setNetworkingCommandsEnabled(isOnlineMode());
ui->toolButton_fetch->setDot(getRemoteChanged());
Git::Branch b = currentBranch();
ui->toolButton_push->setNumber(b.ahead > 0 ? b.ahead : -1);
ui->toolButton_pull->setNumber(b.behind > 0 ? b.behind : -1);
{
bool f = isRepositoryOpened();
ui->toolButton_status->setEnabled(f);
ui->toolButton_terminal->setEnabled(f);
ui->toolButton_explorer->setEnabled(f);
ui->action_repository_status->setEnabled(f);
ui->action_terminal->setEnabled(f);
ui->action_explorer->setEnabled(f);
}
}
void MainWindow::updateStatusBarText(RepositoryWrapperFrame *frame)
{
QString text;
QWidget *w = qApp->focusWidget();
if (w == ui->treeWidget_repos) {
RepositoryItem const *repo = selectedRepositoryItem();
if (repo) {
text = QString("%1 : %2")
.arg(repo->name)
.arg(misc::normalizePathSeparator(repo->local_dir))
;
}
} else if (w == frame->logtablewidget()) {
QTableWidgetItem *item = frame->logtablewidget()->item(selectedLogIndex(frame), 0);
if (item) {
auto const &logs = getLogs(frame);
int row = item->data(IndexRole).toInt();
if (row < (int)logs.size()) {
Git::CommitItem const &commit = logs[row];
if (Git::isUncommited(commit)) {
text = tr("Uncommited changes");
} else {
QString id = commit.commit_id;
text = QString("%1 : %2%3")
.arg(id.mid(0, 7))
.arg(commit.message)
.arg(makeCommitInfoText(frame, row, nullptr))
;
}
}
}
}
setStatusBarText(text);
}
-void MainWindow::mergeBranch(QString const &commit, Git::MergeFastForward ff)
+void MainWindow::mergeBranch(QString const &commit, Git::MergeFastForward ff, bool squash)
{
if (commit.isEmpty()) return;
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
- g->mergeBranch(commit, ff);
+ g->mergeBranch(commit, ff, squash);
openRepository(true);
}
-void MainWindow::mergeBranch(Git::CommitItem const *commit, Git::MergeFastForward ff)
+void MainWindow::mergeBranch(Git::CommitItem const *commit, Git::MergeFastForward ff, bool squash)
{
if (!commit) return;
- mergeBranch(commit->commit_id, ff);
+ mergeBranch(commit->commit_id, ff, squash);
}
void MainWindow::rebaseBranch(Git::CommitItem const *commit)
{
if (!commit) return;
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
QString text = tr("Are you sure you want to rebase the commit ?");
text += "\n\n";
text += "> git rebase " + commit->commit_id;
int r = QMessageBox::information(this, tr("Rebase"), text, QMessageBox::Ok, QMessageBox::Cancel);
if (r == QMessageBox::Ok) {
g->rebaseBranch(commit->commit_id);
openRepository(true);
}
}
void MainWindow::cherrypick(Git::CommitItem const *commit)
{
if (!commit) return;
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
int n = commit->parent_ids.size();
if (n == 1) {
g->cherrypick(commit->commit_id);
} else if (n > 1) {
Git::CommitItem head;
Git::CommitItem pick;
g->queryCommit(g->rev_parse("HEAD"), &head);
g->queryCommit(commit->commit_id, &pick);
QList<Git::CommitItem> parents;
for (int i = 0; i < n; i++) {
QString id = commit->commit_id + QString("^%1").arg(i + 1);
id = g->rev_parse(id);
Git::CommitItem item;
g->queryCommit(id, &item);
parents.push_back(item);
}
CherryPickDialog dlg(this, head, pick, parents);
if (dlg.exec() == QDialog::Accepted) {
QString cmd = "-m %1 ";
cmd = cmd.arg(dlg.number());
if (dlg.allowEmpty()) {
cmd += "--allow-empty ";
}
cmd += commit->commit_id;
g->cherrypick(cmd);
} else {
return;
}
}
openRepository(true);
}
void MainWindow::merge(RepositoryWrapperFrame *frame, Git::CommitItem const *commit)
{
if (isThereUncommitedChanges()) return;
if (!commit) {
int row = selectedLogIndex(frame);
commit = commitItem(frame, row);
if (!commit) return;
}
if (!Git::isValidID(commit->commit_id)) return;
static const char MergeFastForward[] = "MergeFastForward";
QString fastforward;
{
MySettings s;
s.beginGroup("Behavior");
fastforward = s.value(MergeFastForward).toString();
s.endGroup();
}
std::vector<QString> labels;
{
int row = selectedLogIndex(frame);
QList<BranchLabel> const *v = label(frame, row);
for (BranchLabel const &label : *v) {
if (label.kind == BranchLabel::LocalBranch || label.kind == BranchLabel::Tag) {
labels.push_back(label.text);
}
}
std::sort(labels.begin(), labels.end());
labels.erase(std::unique(labels.begin(), labels.end()), labels.end());
}
labels.push_back(commit->commit_id);
QString branch_name = currentBranchName();
MergeDialog dlg(fastforward, labels, branch_name, this);
if (dlg.exec() == QDialog::Accepted) {
fastforward = dlg.getFastForwardPolicy();
+ bool squash = dlg.isSquashEnabled();
{
MySettings s;
s.beginGroup("Behavior");
s.setValue(MergeFastForward, fastforward);
s.endGroup();
}
QString from = dlg.mergeFrom();
- mergeBranch(from, MergeDialog::ff(fastforward));
+ mergeBranch(from, MergeDialog::ff(fastforward), squash);
}
}
void MainWindow::showStatus()
{
auto g = git();
if (!g->isValidWorkingCopy()) {
msgNoRepositorySelected();
return;
}
QString s = g->status();
TextEditDialog dlg(this);
dlg.setWindowTitle(tr("Status"));
dlg.setText(s, true);
dlg.exec();
}
void MainWindow::on_action_commit_triggered()
{
commit(frame());
}
void MainWindow::on_action_fetch_triggered()
{
if (isOnlineMode()) {
reopenRepository(true, [&](GitPtr g){
fetch(g, false);
});
} else {
updateRepository();
}
}
void MainWindow::on_action_fetch_prune_triggered()
{
if (!isOnlineMode()) return;
reopenRepository(true, [&](GitPtr g){
fetch(g, true);
});
}
void MainWindow::on_action_push_triggered()
{
push();
}
void MainWindow::on_action_pull_triggered()
{
if (!isOnlineMode()) return;
reopenRepository(true, [&](GitPtr g){
setPtyCondition(PtyCondition::Pull);
setPtyProcessOk(true);
g->pull(getPtyProcess());
while (1) {
if (getPtyProcess()->wait(1)) break;
QApplication::processEvents();
}
});
}
void MainWindow::on_toolButton_push_clicked()
{
ui->action_push->trigger();
}
void MainWindow::on_toolButton_pull_clicked()
{
ui->action_pull->trigger();
}
void MainWindow::on_toolButton_status_clicked()
{
showStatus();
}
void MainWindow::on_action_repository_status_triggered()
{
showStatus();
}
void MainWindow::on_treeWidget_repos_currentItemChanged(QTreeWidgetItem * /*current*/, QTreeWidgetItem * /*previous*/)
{
updateStatusBarText(frame());
}
void MainWindow::on_treeWidget_repos_itemDoubleClicked(QTreeWidgetItem * /*item*/, int /*column*/)
{
openSelectedRepository();
}
void MainWindow::execCommitPropertyDialog(QWidget *parent, RepositoryWrapperFrame *frame, Git::CommitItem const *commit)
{
CommitPropertyDialog dlg(parent, this, frame, commit);
dlg.exec();
}
int MainWindow::indexOfRepository(QTreeWidgetItem const *treeitem) const
{
if (!treeitem) return -1;
return treeitem->data(0, IndexRole).toInt();
}
void MainWindow::on_treeWidget_repos_customContextMenuRequested(const QPoint &pos)
{
QTreeWidgetItem *treeitem = ui->treeWidget_repos->currentItem();
if (!treeitem) return;
RepositoryItem const *repo = repositoryItem(treeitem);
int index = indexOfRepository(treeitem);
if (isGroupItem(treeitem)) { // group item
QMenu menu;
QAction *a_add_new_group = menu.addAction(tr("&Add new group"));
QAction *a_delete_group = menu.addAction(tr("&Delete group"));
QAction *a_rename_group = menu.addAction(tr("&Rename group"));
QPoint pt = ui->treeWidget_repos->mapToGlobal(pos);
QAction *a = menu.exec(pt + QPoint(8, -8));
if (a) {
if (a == a_add_new_group) {
QTreeWidgetItem *child = newQTreeWidgetFolderItem(tr("New group"));
treeitem->addChild(child);
child->setFlags(child->flags() | Qt::ItemIsEditable);
ui->treeWidget_repos->setCurrentItem(child);
return;
}
if (a == a_delete_group) {
QTreeWidgetItem *parent = treeitem->parent();
if (parent) {
int i = parent->indexOfChild(treeitem);
delete parent->takeChild(i);
} else {
int i = ui->treeWidget_repos->indexOfTopLevelItem(treeitem);
delete ui->treeWidget_repos->takeTopLevelItem(i);
}
refrectRepositories();
return;
}
if (a == a_rename_group) {
ui->treeWidget_repos->editItem(treeitem);
return;
}
}
} else if (repo) { // repository item
QString open_terminal = tr("Open &terminal");
QString open_commandprompt = tr("Open command promp&t");
QMenu menu;
QAction *a_open = menu.addAction(tr("&Open"));
menu.addSeparator();
#ifdef Q_OS_WIN
QAction *a_open_terminal = menu.addAction(open_commandprompt);
(void)open_terminal;
#else
QAction *a_open_terminal = menu.addAction(open_terminal);
(void)open_commandprompt;
#endif
a_open_terminal->setIcon(QIcon(":/image/terminal.svg"));
QAction *a_open_folder = menu.addAction(tr("Open &folder"));
a_open_folder->setIcon(QIcon(":/image/explorer.svg"));
menu.addSeparator();
QAction *a_remove = menu.addAction(tr("&Remove"));
menu.addSeparator();
QAction *a_properties = addMenuActionProperty(&menu);
QPoint pt = ui->treeWidget_repos->mapToGlobal(pos);
QAction *a = menu.exec(pt + QPoint(8, -8));
if (a) {
if (a == a_open) {
openSelectedRepository();
return;
}
if (a == a_open_folder) {
openExplorer(repo);
return;
}
if (a == a_open_terminal) {
openTerminal(repo);
return;
}
if (a == a_remove) {
removeRepositoryFromBookmark(index, true);
return;
}
if (a == a_properties) {
execRepositoryPropertyDialog(*repo);
return;
}
}
}
}
void MainWindow::on_tableWidget_log_customContextMenuRequested(const QPoint &pos)
{
int row = selectedLogIndex(frame());
Git::CommitItem const *commit = commitItem(frame(), row);
if (commit) {
bool is_valid_commit_id = Git::isValidID(commit->commit_id);
QMenu menu;
QAction *a_copy_id_7letters = is_valid_commit_id ? menu.addAction(tr("Copy commit id (7 letters)")) : nullptr;
QAction *a_copy_id_complete = is_valid_commit_id ? menu.addAction(tr("Copy commit id (completely)")) : nullptr;
std::set<QAction *> copy_label_actions;
{
QList<BranchLabel> v = sortedLabels(frame(), row);
if (!v.isEmpty()) {
auto *copy_lebel_menu = menu.addMenu("Copy label");
for (BranchLabel const &l : v) {
QAction *a = copy_lebel_menu->addAction(l.text);
copy_label_actions.insert(copy_label_actions.end(), a);
}
}
}
menu.addSeparator();
QAction *a_checkout = menu.addAction(tr("Checkout/Branch..."));
menu.addSeparator();
QAction *a_edit_message = nullptr;
auto canEditMessage = [&](){
if (commit->has_child) return false; // 子がないこと
if (Git::isUncommited(*commit)) return false; // 未コミットがないこと
bool is_head = false;
bool has_remote_branch = false;
QList<BranchLabel> const *labels = label(frame(), row);
for (const BranchLabel &label : *labels) {
if (label.kind == BranchLabel::Head) {
is_head = true;
} else if (label.kind == BranchLabel::RemoteBranch) {
has_remote_branch = true;
}
}
return is_head && !has_remote_branch; // HEAD && リモートブランチ無し
};
if (canEditMessage()) {
a_edit_message = menu.addAction(tr("Edit message..."));
}
QAction *a_merge = is_valid_commit_id ? menu.addAction(tr("Merge")) : nullptr;
QAction *a_rebase = is_valid_commit_id ? menu.addAction(tr("Rebase")) : nullptr;
QAction *a_cherrypick = is_valid_commit_id ? menu.addAction(tr("Cherry-pick")) : nullptr;
QAction *a_edit_tags = is_valid_commit_id ? menu.addAction(tr("Edit tags...")) : nullptr;
QAction *a_revert = is_valid_commit_id ? menu.addAction(tr("Revert")) : nullptr;
menu.addSeparator();
QAction *a_delbranch = is_valid_commit_id ? menu.addAction(tr("Delete branch...")) : nullptr;
QAction *a_delrembranch = remoteBranches(frame(), commit->commit_id, nullptr).isEmpty() ? nullptr : menu.addAction(tr("Delete remote branch..."));
menu.addSeparator();
QAction *a_explore = is_valid_commit_id ? menu.addAction(tr("Explore")) : nullptr;
QAction *a_properties = addMenuActionProperty(&menu);
QAction *a = menu.exec(frame()->logtablewidget()->viewport()->mapToGlobal(pos) + QPoint(8, -8));
if (a) {
if (a == a_copy_id_7letters) {
qApp->clipboard()->setText(commit->commit_id.mid(0, 7));
return;
}
if (a == a_copy_id_complete) {
qApp->clipboard()->setText(commit->commit_id);
return;
}
if (a == a_properties) {
execCommitPropertyDialog(this, frame(), commit);
return;
}
if (a == a_edit_message) {
commitAmend(frame());
return;
}
if (a == a_checkout) {
checkout(frame(), this, commit);
return;
}
if (a == a_delbranch) {
deleteBranch(frame(), commit);
return;
}
if (a == a_delrembranch) {
deleteRemoteBranch(frame(), commit);
return;
}
if (a == a_merge) {
merge(frame(), commit);
return;
}
if (a == a_rebase) {
rebaseBranch(commit);
return;
}
if (a == a_cherrypick) {
cherrypick(commit);
return;
}
if (a == a_edit_tags) {
ui->action_edit_tags->trigger();
return;
}
if (a == a_revert) {
revertCommit(frame());
return;
}
if (a == a_explore) {
execCommitExploreWindow(frame(), this, commit);
return;
}
if (copy_label_actions.find(a) != copy_label_actions.end()) {
QString text = a->text();
QApplication::clipboard()->setText(text);
return;
}
}
}
}
void MainWindow::on_listWidget_files_customContextMenuRequested(const QPoint &pos)
{
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
QMenu menu;
QAction *a_delete = menu.addAction(tr("Delete"));
QAction *a_untrack = menu.addAction(tr("Untrack"));
QAction *a_history = menu.addAction(tr("History"));
QAction *a_blame = menu.addAction(tr("Blame"));
QAction *a_properties = addMenuActionProperty(&menu);
QPoint pt = frame()->fileslistwidget()->mapToGlobal(pos) + QPoint(8, -8);
QAction *a = menu.exec(pt);
if (a) {
QListWidgetItem *item = frame()->fileslistwidget()->currentItem();
if (a == a_delete) {
if (askAreYouSureYouWantToRun("Delete", tr("Delete selected files."))) {
for_each_selected_files([&](QString const &path){
g->removeFile(path);
g->chdirexec([&](){
QFile(path).remove();
return true;
});
});
openRepository(false);
}
} else if (a == a_untrack) {
if (askAreYouSureYouWantToRun("Untrack", tr("rm --cached files"))) {
for_each_selected_files([&](QString const &path){
g->rm_cached(path);
});
openRepository(false);
}
} else if (a == a_history) {
execFileHistory(item);
} else if (a == a_blame) {
blame(item);
} else if (a == a_properties) {
showObjectProperty(item);
}
}
}
void MainWindow::on_listWidget_unstaged_customContextMenuRequested(const QPoint &pos)
{
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
QList<QListWidgetItem *> items = frame()->unstagedFileslistwidget()->selectedItems();
if (!items.isEmpty()) {
QMenu menu;
QAction *a_stage = menu.addAction(tr("Stage"));
QAction *a_reset_file = menu.addAction(tr("Reset"));
QAction *a_ignore = menu.addAction(tr("Ignore"));
QAction *a_delete = menu.addAction(tr("Delete"));
QAction *a_untrack = menu.addAction(tr("Untrack"));
QAction *a_history = menu.addAction(tr("History"));
QAction *a_blame = menu.addAction(tr("Blame"));
QAction *a_properties = addMenuActionProperty(&menu);
QPoint pt = frame()->unstagedFileslistwidget()->mapToGlobal(pos) + QPoint(8, -8);
QAction *a = menu.exec(pt);
if (a) {
QListWidgetItem *item = frame()->unstagedFileslistwidget()->currentItem();
if (a == a_stage) {
for_each_selected_files([&](QString const &path){
g->stage(path);
});
updateCurrentFilesList(frame());
return;
}
if (a == a_reset_file) {
QStringList paths;
for_each_selected_files([&](QString const &path){
paths.push_back(path);
});
resetFile(paths);
return;
}
if (a == a_ignore) {
QString gitignore_path = currentWorkingCopyDir() / ".gitignore";
if (items.size() == 1) {
QString file = getFilePath(items[0]);
EditGitIgnoreDialog dlg(this, gitignore_path, file);
if (dlg.exec() == QDialog::Accepted) {
QString appending = dlg.text();
if (!appending.isEmpty()) {
QString text;
QString path = gitignore_path;
path.replace('/', QDir::separator());
{
QFile file(path);
if (file.open(QFile::ReadOnly)) {
text += QString::fromUtf8(file.readAll());
}
}
size_t n = text.size();
if (n > 0 && text[(int)n - 1] != '\n') {
text += '\n'; // 最後に改行を追加
}
text += appending + '\n';
{
QFile file(path);
if (file.open(QFile::WriteOnly)) {
file.write(text.toUtf8());
}
}
updateCurrentFilesList(frame());
return;
}
} else {
return;
}
}
QString append;
for_each_selected_files([&](QString const &path){
if (path == ".gitignore") {
// skip
} else {
append += path + '\n';
}
});
if (TextEditDialog::editFile(this, gitignore_path, ".gitignore", append)) {
updateCurrentFilesList(frame());
}
return;
}
if (a == a_delete) {
if (askAreYouSureYouWantToRun("Delete", "Delete selected files.")) {
for_each_selected_files([&](QString const &path){
g->removeFile(path);
g->chdirexec([&](){
QFile(path).remove();
return true;
});
});
openRepository(false);
}
return;
}
if (a == a_untrack) {
if (askAreYouSureYouWantToRun("Untrack", "rm --cached")) {
for_each_selected_files([&](QString const &path){
g->rm_cached(path);
});
openRepository(false);
}
return;
}
if (a == a_history) {
execFileHistory(item);
return;
}
if (a == a_blame) {
blame(item);
return;
}
if (a == a_properties) {
showObjectProperty(item);
return;
}
}
}
}
void MainWindow::on_listWidget_staged_customContextMenuRequested(const QPoint &pos)
{
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
QListWidgetItem *item = frame()->stagedFileslistwidget()->currentItem();
if (item) {
QString path = getFilePath(item);
QString fullpath = currentWorkingCopyDir() / path;
if (QFileInfo(fullpath).isFile()) {
QMenu menu;
QAction *a_unstage = menu.addAction(tr("Unstage"));
QAction *a_history = menu.addAction(tr("History"));
QAction *a_blame = menu.addAction(tr("Blame"));
QAction *a_properties = addMenuActionProperty(&menu);
QPoint pt = frame()->stagedFileslistwidget()->mapToGlobal(pos) + QPoint(8, -8);
QAction *a = menu.exec(pt);
if (a) {
QListWidgetItem *item = frame()->unstagedFileslistwidget()->currentItem();
if (a == a_unstage) {
g->unstage(path);
openRepository(false);
} else if (a == a_history) {
execFileHistory(item);
} else if (a == a_blame) {
blame(item);
} else if (a == a_properties) {
showObjectProperty(item);
}
}
}
}
}
QStringList MainWindow::selectedFiles_(QListWidget *listwidget) const
{
QStringList list;
QList<QListWidgetItem *> items = listwidget->selectedItems();
for (QListWidgetItem *item : items) {
QString path = getFilePath(item);
list.push_back(path);
}
return list;
}
QStringList MainWindow::selectedFiles() const
{
if (m2->last_focused_file_list == ui->listWidget_files) return selectedFiles_(ui->listWidget_files);
if (m2->last_focused_file_list == ui->listWidget_staged) return selectedFiles_(ui->listWidget_staged);
if (m2->last_focused_file_list == ui->listWidget_unstaged) return selectedFiles_(ui->listWidget_unstaged);
return QStringList();
}
void MainWindow::for_each_selected_files(std::function<void(QString const&)> const &fn)
{
for (QString const &path : selectedFiles()) {
fn(path);
}
}
void MainWindow::execFileHistory(QListWidgetItem *item)
{
if (item) {
QString path = getFilePath(item);
if (!path.isEmpty()) {
execFileHistory(path);
}
}
}
/**
* @brief オブジェクトプロパティ
* @param item
*/
void MainWindow::showObjectProperty(QListWidgetItem *item)
{
if (item) {
QString submodpath = getSubmodulePath(item);
if (!submodpath.isEmpty()) {
// サブモジュールウィンドウを表示する
Git::SubmoduleItem submod;
submod.path = submodpath;
submod.id = getObjectID(item);
if (submod) {
OverrideWaitCursor;
GitPtr g = git(submod);
SubmoduleMainWindow *w = new SubmoduleMainWindow(this, g);
w->show();
w->reset();
}
} else {
// ファイルプロパティダイアログを表示する
QString path = getFilePath(item);
QString id = getObjectID(item);
FilePropertyDialog dlg(this);
dlg.exec(this, path, id);
}
}
}
void MainWindow::checkout(RepositoryWrapperFrame *frame, QWidget *parent, const Git::CommitItem *commit, std::function<void ()> accepted_callback)
{
if (!commit) return;
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
QStringList tags;
QStringList all_local_branches;
QStringList local_branches;
QStringList remote_branches;
{
NamedCommitList named_commits = namedCommitItems(frame, Branches | Tags | Remotes);
for (NamedCommitItem const &item : named_commits) {
QString name = item.name;
if (item.id == commit->commit_id) {
if (item.type == NamedCommitItem::Type::Tag) {
tags.push_back(name);
} else if (item.type == NamedCommitItem::Type::BranchLocal || item.type == NamedCommitItem::Type::BranchRemote) {
int i = name.lastIndexOf('/');
if (i < 0 && name == "HEAD") continue;
if (i > 0 && name.mid(i + 1) == "HEAD") continue;
if (item.type == NamedCommitItem::Type::BranchLocal) {
local_branches.push_back(name);
} else if (item.type == NamedCommitItem::Type::BranchRemote) {
remote_branches.push_back(name);
}
}
}
if (item.type == NamedCommitItem::Type::BranchLocal) {
all_local_branches.push_back(name);
}
}
}
CheckoutDialog dlg(parent, tags, all_local_branches, local_branches, remote_branches);
if (dlg.exec() == QDialog::Accepted) {
if (accepted_callback) {
accepted_callback();
}
CheckoutDialog::Operation op = dlg.operation();
QString name = dlg.branchName();
QString id = commit->commit_id;
if (id.isEmpty() && !commit->parent_ids.isEmpty()) {
id = commit->parent_ids.front();
}
bool ok = false;
setLogEnabled(g, true);
if (op == CheckoutDialog::Operation::HeadDetached) {
if (!id.isEmpty()) {
ok = g->git(QString("checkout \"%1\"").arg(id), true);
}
} else if (op == CheckoutDialog::Operation::CreateLocalBranch) {
if (!name.isEmpty() && !id.isEmpty()) {
ok = g->git(QString("checkout -b \"%1\" \"%2\"").arg(name).arg(id), true);
}
} else if (op == CheckoutDialog::Operation::ExistingLocalBranch) {
if (!name.isEmpty()) {
ok = g->git(QString("checkout \"%1\"").arg(name), true);
}
}
if (ok) {
openRepository(true);
}
}
}
void MainWindow::checkout(RepositoryWrapperFrame *frame)
{
checkout(frame, this, selectedCommitItem(frame));
}
/**
* @brief コミットログの選択が変化した
*/
void MainWindow::doLogCurrentItemChanged(RepositoryWrapperFrame *frame)
{
clearFileList(frame);
int row = selectedLogIndex(frame);
QTableWidgetItem *item = frame->logtablewidget()->item(row, 0);
if (item) {
auto const &logs = getLogs(frame);
int index = item->data(IndexRole).toInt();
if (index < (int)logs.size()) {
// ステータスバー更新
updateStatusBarText(frame);
// 少し待ってファイルリストを更新する
postUserFunctionEvent([&](QVariant const &, void *p){
RepositoryWrapperFrame *frame = reinterpret_cast<RepositoryWrapperFrame *>(p);
updateCurrentFilesList(frame);
}, {}, reinterpret_cast<void *>(frame), 300); // 300ms後(キーボードのオートリピート想定)
}
} else {
row = -1;
}
updateAncestorCommitMap(frame);
frame->logtablewidget()->viewport()->update();
}
void MainWindow::findNext(RepositoryWrapperFrame *frame)
{
if (m2->search_text.isEmpty()) {
return;
}
auto const &logs = getLogs(frame);
for (int pass = 0; pass < 2; pass++) {
int row = 0;
if (pass == 0) {
row = selectedLogIndex(frame);
if (row < 0) {
row = 0;
} else if (m2->searching) {
row++;
}
}
while (row < (int)logs.size()) {
Git::CommitItem const commit = logs[row];
if (!Git::isUncommited(commit)) {
if (commit.message.indexOf(m2->search_text, 0, Qt::CaseInsensitive) >= 0) {
bool b = frame->logtablewidget()->blockSignals(true);
setCurrentLogRow(frame, row);
frame->logtablewidget()->blockSignals(b);
m2->searching = true;
return;
}
}
row++;
}
}
}
void MainWindow::findText(QString const &text)
{
m2->search_text = text;
}
bool MainWindow::isAncestorCommit(QString const &id)
{
auto it = m2->ancestors.find(id);
return it != m2->ancestors.end();
}
void MainWindow::updateAncestorCommitMap(RepositoryWrapperFrame *frame)
{
m2->ancestors.clear();
auto const &logs = getLogs(frame);
const size_t LogCount = logs.size();
const size_t index = selectedLogIndex(frame);
if (index < LogCount) {
// ok
} else {
return;
}
auto *logsp = getLogsPtr(frame);
auto LogItem = [&](size_t i)->Git::CommitItem &{ return logsp->at(i); };
std::map<QString, size_t> commit_to_index_map;
size_t end = LogCount;
if (index < end) {
for (size_t i = index; i < end; i++) {
Git::CommitItem const &commit = LogItem(i);
commit_to_index_map[commit.commit_id] = i;
auto *item = frame->logtablewidget()->item(i, 0);
QRect r = frame->logtablewidget()->visualItemRect(item);
if (r.y() >= frame->logtablewidget()->height()) {
end = i + 1;
break;
}
}
}
Git::CommitItem *item = &LogItem(index);
if (item) {
m2->ancestors.insert(m2->ancestors.end(), item->commit_id);
}
for (size_t i = index; i < end; i++) {
Git::CommitItem *item = &LogItem(i);
if (isAncestorCommit(item->commit_id)) {
for (QString const &parent : item->parent_ids) {
m2->ancestors.insert(m2->ancestors.end(), parent);
}
}
}
}
void MainWindow::on_action_open_existing_working_copy_triggered()
{
QString dir = defaultWorkingDir();
dir = QFileDialog::getExistingDirectory(this, tr("Add existing working copy"), dir);
addWorkingCopyDir(dir, false);
}
void MainWindow::on_action_view_refresh_triggered()
{
openRepository(true);
}
void MainWindow::on_tableWidget_log_currentItemChanged(QTableWidgetItem * /*current*/, QTableWidgetItem * /*previous*/)
{
doLogCurrentItemChanged(frame());
m2->searching = false;
}
void MainWindow::on_toolButton_stage_clicked()
{
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
g->stage(selectedFiles());
updateCurrentFilesList(frame());
}
void MainWindow::on_toolButton_unstage_clicked()
{
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
g->unstage(selectedFiles());
updateCurrentFilesList(frame());
}
void MainWindow::on_toolButton_select_all_clicked()
{
if (frame()->unstagedFileslistwidget()->count() > 0) {
frame()->unstagedFileslistwidget()->setFocus();
frame()->unstagedFileslistwidget()->selectAll();
} else if (frame()->stagedFileslistwidget()->count() > 0) {
frame()->stagedFileslistwidget()->setFocus();
frame()->stagedFileslistwidget()->selectAll();
}
}
void MainWindow::on_toolButton_commit_clicked()
{
ui->action_commit->trigger();
}
void MainWindow::on_action_edit_global_gitconfig_triggered()
{
QString dir = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
QString path = dir / ".gitconfig";
editFile(path, ".gitconfig");
}
void MainWindow::on_action_edit_git_config_triggered()
{
QString dir = currentWorkingCopyDir();
QString path = dir / ".git/config";
editFile(path, ".git/config");
}
void MainWindow::on_action_edit_gitignore_triggered()
{
QString dir = currentWorkingCopyDir();
QString path = dir / ".gitignore";
if (editFile(path, ".gitignore")) {
updateCurrentFilesList(frame());
}
}
int MainWindow::selectedLogIndex(RepositoryWrapperFrame *frame) const
{
auto const &logs = getLogs(frame);
int i = frame->logtablewidget()->currentRow();
if (i >= 0 && i < (int)logs.size()) {
return i;
}
return -1;
}
/**
* @brief ファイル差分表示を更新する
* @param item
*/
void MainWindow::updateDiffView(RepositoryWrapperFrame *frame, QListWidgetItem *item)
{
clearDiffView(frame);
m2->last_selected_file_item = item;
if (!item) return;
int idiff = indexOfDiff(item);
if (idiff >= 0 && idiff < diffResult()->size()) {
Git::Diff const &diff = diffResult()->at(idiff);
QString key = GitDiff::makeKey(diff);
auto it = getDiffCacheMap(frame)->find(key);
if (it != getDiffCacheMap(frame)->end()) {
auto const &logs = getLogs(frame);
int row = frame->logtablewidget()->currentRow();
bool uncommited = (row >= 0 && row < (int)logs.size() && Git::isUncommited(logs[row]));
frame->filediffwidget()->updateDiffView(it->second, uncommited);
}
}
}
void MainWindow::updateDiffView(RepositoryWrapperFrame *frame)
{
updateDiffView(frame, m2->last_selected_file_item);
}
void MainWindow::updateUnstagedFileCurrentItem(RepositoryWrapperFrame *frame)
{
updateDiffView(frame, frame->unstagedFileslistwidget()->currentItem());
}
void MainWindow::updateStagedFileCurrentItem(RepositoryWrapperFrame *frame)
{
updateDiffView(frame, frame->stagedFileslistwidget()->currentItem());
}
void MainWindow::on_listWidget_unstaged_currentRowChanged(int /*currentRow*/)
{
updateUnstagedFileCurrentItem(frame());
}
void MainWindow::on_listWidget_staged_currentRowChanged(int /*currentRow*/)
{
updateStagedFileCurrentItem(frame());
}
void MainWindow::on_listWidget_files_currentRowChanged(int /*currentRow*/)
{
updateDiffView(frame(), frame()->fileslistwidget()->currentItem());
}
void MainWindow::dragEnterEvent(QDragEnterEvent *event)
{
if (QApplication::modalWindow()) return;
if (event->mimeData()->hasUrls()) {
event->acceptProposedAction();
event->accept();
}
}
void MainWindow::keyPressEvent(QKeyEvent *event)
{
int c = event->key();
if (c == Qt::Key_T && (event->modifiers() & Qt::ControlModifier)) {
test();
return;
}
if (QApplication::focusWidget() == ui->widget_log) {
auto write_char = [&](char c){
if (getPtyProcess()->isRunning()) {
getPtyProcess()->writeInput(&c, 1);
}
};
auto write_text = [&](QString const &str){
std::string s = str.toStdString();
for (char i : s) {
write_char(i);
}
};
if (c == Qt::Key_Return || c == Qt::Key_Enter) {
write_char('\n');
} else {
QString text = event->text();
write_text(text);
}
}
}
void MainWindow::on_action_edit_settings_triggered()
{
SettingsDialog dlg(this);
if (dlg.exec() == QDialog::Accepted) {
ApplicationSettings const &newsettings = dlg.settings();
setAppSettings(newsettings);
setGitCommand(appsettings()->git_command, false);
setFileCommand(appsettings()->file_command, false);
setGpgCommand(appsettings()->gpg_command, false);
}
}
void MainWindow::onCloneCompleted(bool success, QVariant const &userdata)
{
if (success) {
RepositoryItem r = userdata.value<RepositoryItem>();
saveRepositoryBookmark(r);
setCurrentRepository(r, false);
openRepository(true);
}
}
void MainWindow::onPtyProcessCompleted(bool /*ok*/, QVariant const &userdata)
{
switch (getPtyCondition()) {
case PtyCondition::Clone:
onCloneCompleted(getPtyProcessOk(), userdata);
break;
}
setPtyCondition(PtyCondition::None);
}
void MainWindow::on_action_clone_triggered()
{
clone();
}
void MainWindow::on_action_about_triggered()
{
AboutDialog dlg(this);
dlg.exec();
}
void MainWindow::on_toolButton_clone_clicked()
{
ui->action_clone->trigger();
}
void MainWindow::on_toolButton_fetch_clicked()
{
ui->action_fetch->trigger();
}
void MainWindow::clearRepoFilter()
{
ui->lineEdit_filter->clear();
}
void MainWindow::appendCharToRepoFilter(ushort c)
{
if (QChar(c).isLetter()) {
c = QChar(c).toLower().unicode();
}
ui->lineEdit_filter->setText(getRepositoryFilterText() + c);
}
void MainWindow::backspaceRepoFilter()
{
QString s = getRepositoryFilterText();
int n = s.size();
if (n > 0) {
s = s.mid(0, n - 1);
}
ui->lineEdit_filter->setText(s);
}
void MainWindow::on_lineEdit_filter_textChanged(QString const &text)
{
setRepositoryFilterText(text);
updateRepositoriesList();
}
void MainWindow::on_toolButton_erase_filter_clicked()
{
clearRepoFilter();
ui->lineEdit_filter->setFocus();
}
void MainWindow::deleteTags(RepositoryWrapperFrame *frame, QStringList const &tagnames)
{
int row = frame->logtablewidget()->currentRow();
internalDeleteTags(tagnames);
frame->logtablewidget()->selectRow(row);
}
void MainWindow::revertCommit(RepositoryWrapperFrame *frame)
{
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
Git::CommitItem const *commit = selectedCommitItem(frame);
if (commit) {
g->revert(commit->commit_id);
openRepository(false);
}
}
bool MainWindow::addTag(RepositoryWrapperFrame *frame, QString const &name)
{
int row = frame->logtablewidget()->currentRow();
bool ok = internalAddTag(frame, name);
frame->selectLogTableRow(row);
return ok;
}
void MainWindow::on_action_push_all_tags_triggered()
{
reopenRepository(false, [&](GitPtr g){
g->push(true);
});
}
void MainWindow::on_tableWidget_log_itemDoubleClicked(QTableWidgetItem *)
{
Git::CommitItem const *commit = selectedCommitItem(frame());
if (commit) {
execCommitPropertyDialog(this, frame(), commit);
}
}
void MainWindow::on_listWidget_unstaged_itemDoubleClicked(QListWidgetItem * item)
{
showObjectProperty(item);
}
void MainWindow::on_listWidget_staged_itemDoubleClicked(QListWidgetItem *item)
{
showObjectProperty(item);
}
void MainWindow::on_listWidget_files_itemDoubleClicked(QListWidgetItem *item)
{
showObjectProperty(item);
}
QListWidgetItem *MainWindow::currentFileItem() const
{
QListWidget *listwidget = nullptr;
if (ui->stackedWidget_filelist->currentWidget() == ui->page_uncommited) {
QWidget *w = qApp->focusWidget();
if (w == ui->listWidget_unstaged) {
listwidget = ui->listWidget_unstaged;
} else if (w == ui->listWidget_staged) {
listwidget = ui->listWidget_staged;
}
} else {
listwidget = ui->listWidget_files;
}
if (listwidget) {
return listwidget->currentItem();
}
return nullptr;
}
void MainWindow::on_action_set_config_user_triggered()
{
Git::User global_user;
Git::User repo_user;
GitPtr g = git();
if (isValidWorkingCopy(g)) {
repo_user = g->getUser(Git::Source::Local);
}
global_user = g->getUser(Git::Source::Global);
execSetUserDialog(global_user, repo_user, currentRepositoryName());
}
void MainWindow::showLogWindow(bool show)
{
ui->dockWidget_log->setVisible(show);
}
void MainWindow::on_action_window_log_triggered(bool checked)
{
showLogWindow(checked);
}
void MainWindow::on_action_repo_jump_triggered()
{
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
NamedCommitList items = namedCommitItems(frame(), Branches | Tags | Remotes);
{
NamedCommitItem head;
head.name = "HEAD";
head.id = getHeadId();
items.push_front(head);
}
JumpDialog dlg(this, items);
if (dlg.exec() == QDialog::Accepted) {
QString text = dlg.text();
if (text.isEmpty()) return;
QString id = g->rev_parse(text);
if (id.isEmpty() && Git::isValidID(text)) {
QStringList list = findGitObject(text);
if (list.isEmpty()) {
QMessageBox::warning(this, tr("Jump"), QString("%1\n\n").arg(text) + tr("No such commit"));
return;
}
ObjectBrowserDialog dlg2(this, list);
if (dlg2.exec() == QDialog::Accepted) {
id = dlg2.text();
if (id.isEmpty()) return;
}
}
if (g->objectType(id) == "tag") {
id = getObjCache(frame())->getCommitIdFromTag(id);
}
int row = rowFromCommitId(frame(), id);
if (row < 0) {
QMessageBox::warning(this, tr("Jump"), QString("%1\n(%2)\n\n").arg(text).arg(id) + tr("No such commit"));
} else {
setCurrentLogRow(frame(), row);
}
}
}
void MainWindow::on_action_repo_checkout_triggered()
{
checkout(frame());
}
void MainWindow::on_action_delete_branch_triggered()
{
deleteBranch(frame());
}
void MainWindow::on_toolButton_terminal_clicked()
{
openTerminal(nullptr);
}
void MainWindow::on_toolButton_explorer_clicked()
{
openExplorer(nullptr);
}
void MainWindow::on_action_reset_HEAD_1_triggered()
{
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
g->reset_head1();
openRepository(false);
}
void MainWindow::on_action_create_a_repository_triggered()
{
createRepository(QString());
}
bool MainWindow::isOnlineMode() const
{
return m2->is_online_mode;
}
void MainWindow::setRemoteOnline(bool f, bool save)
{
m2->is_online_mode = f;
{
QRadioButton *rb = nullptr;
rb = f ? ui->radioButton_remote_online : ui->radioButton_remote_offline;
rb->blockSignals(true);
rb->click();
rb->blockSignals(false);
ui->action_online->setCheckable(true);
ui->action_offline->setCheckable(true);
ui->action_online->setChecked(f);
ui->action_offline->setChecked(!f);
setNetworkingCommandsEnabled(f);
}
if (save) {
MySettings s;
s.beginGroup("Remote");
s.setValue("Online", f);
s.endGroup();
}
}
void MainWindow::on_radioButton_remote_online_clicked()
{
setRemoteOnline(true, true);
}
void MainWindow::on_radioButton_remote_offline_clicked()
{
setRemoteOnline(false, true);
}
void MainWindow::on_verticalScrollBar_log_valueChanged(int)
{
ui->widget_log->refrectScrollBar();
}
void MainWindow::on_horizontalScrollBar_log_valueChanged(int)
{
ui->widget_log->refrectScrollBar();
}
void MainWindow::on_toolButton_stop_process_clicked()
{
abortPtyProcess();
}
void MainWindow::on_action_stop_process_triggered()
{
abortPtyProcess();
}
void MainWindow::on_action_exit_triggered()
{
close();
}
void MainWindow::on_action_reflog_triggered()
{
GitPtr g = git();
Git::ReflogItemList reflog;
g->reflog(&reflog);
ReflogWindow dlg(this, this, reflog);
dlg.exec();
}
void MainWindow::blame(QListWidgetItem *item)
{
QList<BlameItem> list;
QString path = getFilePath(item);
{
GitPtr g = git();
QByteArray ba = g->blame(path);
if (!ba.isEmpty()) {
char const *begin = ba.data();
char const *end = begin + ba.size();
list = BlameWindow::parseBlame(begin, end);
}
}
if (!list.isEmpty()) {
qApp->setOverrideCursor(Qt::WaitCursor);
BlameWindow win(this, path, list);
qApp->restoreOverrideCursor();
win.exec();
}
}
void MainWindow::blame()
{
blame(currentFileItem());
}
void MainWindow::on_action_repository_property_triggered()
{
execRepositoryPropertyDialog(currentRepository());
}
void MainWindow::on_action_set_gpg_signing_triggered()
{
GitPtr g = git();
QString global_key_id = g->signingKey(Git::Source::Global);
QString repository_key_id;
if (g->isValidWorkingCopy()) {
repository_key_id = g->signingKey(Git::Source::Local);
}
SetGpgSigningDialog dlg(this, currentRepositoryName(), global_key_id, repository_key_id);
if (dlg.exec() == QDialog::Accepted) {
g->setSigningKey(dlg.id(), dlg.isGlobalChecked());
}
}
void MainWindow::execAreYouSureYouWantToContinueConnectingDialog()
{
using TheDlg = AreYouSureYouWantToContinueConnectingDialog;
setInteractionMode(InteractionMode::Busy);
QApplication::restoreOverrideCursor();
TheDlg dlg(this);
if (dlg.exec() == QDialog::Accepted) {
TheDlg::Result r = dlg.result();
if (r == TheDlg::Result::Yes) {
getPtyProcess()->writeInput("yes\n", 4);
} else {
setPtyProcessOk(false); // abort
getPtyProcess()->writeInput("no\n", 3);
QThread::msleep(300);
stopPtyProcess();
}
} else {
ui->widget_log->setFocus();
setInteractionCanceled(true);
}
setInteractionMode(InteractionMode::Busy);
}
void MainWindow::onLogIdle()
{
if (interactionCanceled()) return;
if (interactionMode() != InteractionMode::None) return;
static char const are_you_sure_you_want_to_continue_connecting[] = "Are you sure you want to continue connecting (yes/no)?";
static char const enter_passphrase[] = "Enter passphrase: ";
static char const enter_passphrase_for_key[] = "Enter passphrase for key '";
static char const fatal_authentication_failed_for[] = "fatal: Authentication failed for '";
std::vector<char> vec;
ui->widget_log->retrieveLastText(&vec, 100);
if (!vec.empty()) {
std::string line;
int n = (int)vec.size();
int i = n;
while (i > 0) {
i--;
if (i + 1 < n && vec[i] == '\n') {
i++;
line.assign(&vec[i], n - i);
break;
}
}
if (!line.empty()) {
auto ExecLineEditDialog = [&](QWidget *parent, QString const &title, QString const &prompt, QString const &val, bool password){
LineEditDialog dlg(parent, title, prompt, val, password);
if (dlg.exec() == QDialog::Accepted) {
std::string ret = dlg.text().toStdString();
std::string str = ret + '\n';
getPtyProcess()->writeInput(str.c_str(), str.size());
return ret;
}
abortPtyProcess();
return std::string();
};
auto Match = [&](char const *str){
int n = strlen(str);
if (strncmp(line.c_str(), str, n) == 0) {
char const *p = line.c_str() + n;
while (1) {
if (!*p) return true;
if (!isspace((unsigned char)*p)) break;
p++;
}
}
return false;
};
auto StartsWith = [&](char const *str){
char const *p = line.c_str();
while (*str) {
if (*p != *str) return false;
str++;
p++;
}
return true;
};
if (Match(are_you_sure_you_want_to_continue_connecting)) {
execAreYouSureYouWantToContinueConnectingDialog();
return;
}
if (line == enter_passphrase) {
ExecLineEditDialog(this, "Passphrase", QString::fromStdString(line), QString(), true);
return;
}
if (StartsWith(enter_passphrase_for_key)) {
std::string keyfile;
{
int i = strlen(enter_passphrase_for_key);
char const *p = line.c_str() + i;
char const *q = strrchr(p, ':');
if (q && p + 2 < q && q[-1] == '\'') {
keyfile.assign(p, q - 1);
}
}
if (!keyfile.empty()) {
if (keyfile == sshPassphraseUser() && !sshPassphrasePass().empty()) {
std::string text = sshPassphrasePass() + '\n';
getPtyProcess()->writeInput(text.c_str(), text.size());
} else {
std::string secret = ExecLineEditDialog(this, "Passphrase for key", QString::fromStdString(line), QString(), true);
sshSetPassphrase(keyfile, secret);
}
return;
}
}
char const *begin = line.c_str();
char const *end = line.c_str() + line.size();
auto Input = [&](QString const &title, bool password, std::string *value){
Q_ASSERT(value);
std::string header = QString("%1 for '").arg(title).toStdString();
if (strncmp(begin, header.c_str(), header.size()) == 0) {
QString msg;
if (memcmp(end - 2, "':", 2) == 0) {
msg = QString::fromUtf8(begin, end - begin - 1);
} else if (memcmp(end - 3, "': ", 3) == 0) {
msg = QString::fromUtf8(begin, end - begin - 2);
}
if (!msg.isEmpty()) {
std::string s = ExecLineEditDialog(this, title, msg, value ? QString::fromStdString(*value) : QString(), password);
*value = s;
return true;
}
}
return false;
};
std::string uid = httpAuthenticationUser();
std::string pwd = httpAuthenticationPass();
bool ok = false;
if (Input("Username", false, &uid)) ok = true;
if (Input("Password", true, &pwd)) ok = true;
if (ok) {
httpSetAuthentication(uid, pwd);
return;
}
if (StartsWith(fatal_authentication_failed_for)) {
QMessageBox::critical(this, tr("Authentication Failed"), QString::fromStdString(line));
abortPtyProcess();
return;
}
}
}
}
void MainWindow::on_action_edit_tags_triggered()
{
Git::CommitItem const *commit = selectedCommitItem(frame());
if (commit && Git::isValidID(commit->commit_id)) {
EditTagsDialog dlg(this, commit);
dlg.exec();
}
}
void MainWindow::on_action_push_u_triggered()
{
pushSetUpstream(false);
}
void MainWindow::on_action_delete_remote_branch_triggered()
{
deleteRemoteBranch(frame(), selectedCommitItem(frame()));
}
void MainWindow::on_action_terminal_triggered()
{
auto const *repo = ¤tRepository();
openTerminal(repo);
}
void MainWindow::on_action_explorer_triggered()
{
auto const *repo = ¤tRepository();
openExplorer(repo);
}
void MainWindow::on_action_reset_hard_triggered()
{
doGitCommand([&](GitPtr g){
g->reset_hard();
});
}
void MainWindow::on_action_clean_df_triggered()
{
doGitCommand([&](GitPtr g){
g->clean_df();
});
}
void MainWindow::postOpenRepositoryFromGitHub(QString const &username, QString const &reponame)
{
QVariantList list;
list.push_back(username);
list.push_back(reponame);
postUserFunctionEvent([&](QVariant const &v, void *){
QVariantList l = v.toList();
QString uname = l[0].toString();
QString rname = l[1].toString();
CloneFromGitHubDialog dlg(this, uname, rname);
if (dlg.exec() == QDialog::Accepted) {
clone(dlg.url());
}
}, QVariant(list));
}
void MainWindow::on_action_stash_triggered()
{
doGitCommand([&](GitPtr g){
g->stash();
});
}
void MainWindow::on_action_stash_apply_triggered()
{
doGitCommand([&](GitPtr g){
g->stash_apply();
});
}
void MainWindow::on_action_stash_drop_triggered()
{
doGitCommand([&](GitPtr g){
g->stash_drop();
});
}
void MainWindow::on_action_online_triggered()
{
ui->radioButton_remote_online->click();
}
void MainWindow::on_action_offline_triggered()
{
ui->radioButton_remote_offline->click();
}
void MainWindow::on_action_repositories_panel_triggered()
{
bool checked = ui->action_repositories_panel->isChecked();
ui->stackedWidget_leftpanel->setCurrentWidget(checked ? ui->page_repos : ui->page_collapsed);
if (checked) {
ui->stackedWidget_leftpanel->setFixedWidth(m2->repos_panel_width);
ui->stackedWidget_leftpanel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
ui->stackedWidget_leftpanel->setMinimumWidth(QWIDGETSIZE_MAX);
ui->stackedWidget_leftpanel->setMaximumWidth(QWIDGETSIZE_MAX);
} else {
m2->repos_panel_width = ui->stackedWidget_leftpanel->width();
ui->stackedWidget_leftpanel->setFixedWidth(24);
}
}
void MainWindow::on_action_find_triggered()
{
m2->searching = false;
if (getLogs(frame()).empty()) {
return;
}
FindCommitDialog dlg(this, m2->search_text);
if (dlg.exec() == QDialog::Accepted) {
m2->search_text = dlg.text();
frame()->setFocusToLogTable();
findNext(frame());
}
}
void MainWindow::on_action_find_next_triggered()
{
if (m2->search_text.isEmpty()) {
on_action_find_triggered();
} else {
findNext(frame());
}
}
void MainWindow::on_action_repo_jump_to_head_triggered()
{
QString name = "HEAD";
GitPtr g = git();
QString id = g->rev_parse(name);
int row = rowFromCommitId(frame(), id);
if (row < 0) {
qDebug() << "No such commit";
} else {
setCurrentLogRow(frame(), row);
}
}
void MainWindow::on_action_repo_merge_triggered()
{
merge(frame());
}
void MainWindow::on_action_expand_commit_log_triggered()
{
ui->splitter_h->setSizes({10000, 1, 1});
}
void MainWindow::on_action_expand_file_list_triggered()
{
ui->splitter_h->setSizes({1, 10000, 1});
}
void MainWindow::on_action_expand_diff_view_triggered()
{
ui->splitter_h->setSizes({1, 1, 10000});
}
void MainWindow::on_action_sidebar_triggered()
{
bool f = ui->stackedWidget_leftpanel->isVisible();
f = !f;
ui->stackedWidget_leftpanel->setVisible(f);
ui->action_sidebar->setChecked(f);
}
#if 0
void MainWindow::on_action_wide_triggered()
{
QWidget *w = focusWidget();
if (w == m->focused_widget) {
ui->splitter_h->setSizes(m->splitter_h_sizes);
m->focused_widget = nullptr;
} else {
m->focused_widget = w;
m->splitter_h_sizes = ui->splitter_h->sizes();
if (w == frame->logtablewidget()) {
ui->splitter_h->setSizes({10000, 1, 1});
} else if (ui->stackedWidget_filelist->isAncestorOf(w)) {
ui->splitter_h->setSizes({1, 10000, 1});
} else if (ui->frame_diff_view->isAncestorOf(w)) {
ui->splitter_h->setSizes({1, 1, 10000});
}
}
}
#endif
void MainWindow::setShowLabels(bool show, bool save)
{
ApplicationSettings *as = appsettings();
as->show_labels = show;
bool b = ui->action_show_labels->blockSignals(true);
ui->action_show_labels->setChecked(show);
ui->action_show_labels->blockSignals(b);
if (save) {
saveApplicationSettings();
}
}
bool MainWindow::isLabelsVisible() const
{
return appsettings()->show_labels;
}
void MainWindow::on_action_show_labels_triggered()
{
bool f = ui->action_show_labels->isChecked();
setShowLabels(f, true);
frame()->updateLogTableView();
}
void MainWindow::on_action_submodules_triggered()
{
GitPtr g = git();
QList<Git::SubmoduleItem> mods = g->submodules();
std::vector<SubmodulesDialog::Submodule> mods2;
mods2.resize(mods.size());
for (size_t i = 0; i < mods.size(); i++) {
const Git::SubmoduleItem mod = mods[i];
mods2[i].submodule = mod;
GitPtr g2 = git(g->workingDir(), mod.path, g->sshKey());
g2->queryCommit(mod.id, &mods2[i].head);
}
SubmodulesDialog dlg(this, mods2);
dlg.exec();
}
void MainWindow::on_action_submodule_add_triggered()
{
QString dir = currentRepository().local_dir;
submodule_add({}, dir);
}
void MainWindow::on_action_submodule_update_triggered()
{
SubmoduleUpdateDialog dlg(this);
if (dlg.exec() == QDialog::Accepted) {
GitPtr g = git();
Git::SubmoduleUpdateData data;
data.init = dlg.isInit();
data.recursive = dlg.isRecursive();
g->submodule_update(data, getPtyProcess());
}
}
/**
* @brief アイコンの読み込みが完了した
*/
void MainWindow::onAvatarUpdated(RepositoryWrapperFrameP frame)
{
updateCommitLogTableLater(frame.pointer, 100); // コミットログを更新(100ms後)
}
void MainWindow::test()
{
}
diff --git a/src/MainWindow.h b/src/MainWindow.h
index eec30a8..2e651fd 100644
--- a/src/MainWindow.h
+++ b/src/MainWindow.h
@@ -1,575 +1,575 @@
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include "BasicMainWindow.h"
#include "Git.h"
#include "GitHubAPI.h"
#include "RepositoryData.h"
#include "MyProcess.h"
#include "main.h"
#include "webclient.h"
#include "AvatarLoader.h"
#include "GitObjectManager.h"
#include "Theme.h"
#include "BranchLabel.h"
class RepositoryWrapperFrame;
class LogTableWidget;
class QListWidgetItem;
class QListWidget;
class QTreeWidgetItem;
class QTableWidgetItem;
namespace Ui {
class MainWindow;
}
class HunkItem {
public:
int hunk_number = -1;
size_t pos, len;
std::vector<std::string> lines;
};
class MainWindow : public BasicMainWindow {
Q_OBJECT
friend class RepositoryWrapperFrame;
friend class SubmoduleMainWindow;
friend class ImageViewWidget;
friend class FileDiffSliderWidget;
friend class FileHistoryWindow;
friend class FileDiffWidget;
friend class AboutDialog;
private:
struct Private1 {
QIcon repository_icon;
QIcon folder_icon;
QIcon signature_good_icon;
QIcon signature_dubious_icon;
QIcon signature_bad_icon;
QPixmap transparent_pixmap;
QString starting_dir;
Git::Context gcx;
RepositoryItem current_repo;
QList<RepositoryItem> repos;
// Git::CommitItemList logs;
QList<Git::Diff> diff_result;
QList<Git::SubmoduleItem> submodules;
QStringList added;
QStringList remotes;
QString current_remote_name;
Git::Branch current_branch;
unsigned int temp_file_counter = 0;
std::string ssh_passphrase_user;
std::string ssh_passphrase_pass;
std::string http_uid;
std::string http_pwd;
std::map<QString, GitHubAPI::User> committer_map; // key is email
PtyProcess pty_process;
bool pty_process_ok = false;
PtyCondition pty_condition = PtyCondition::None;
WebContext webcx;
AvatarLoader avatar_loader;
// int update_files_list_counter = 0;
// int update_commit_table_counter = 0;
bool interaction_canceled = false;
InteractionMode interaction_mode = InteractionMode::None;
QString repository_filter_text;
bool uncommited_changes = false;
// std::map<QString, QList<Git::Branch>> branch_map;
// std::map<QString, QList<Git::Tag>> tag_map;
// std::map<int, QList<BranchLabel>> label_map;
// std::map<QString, Git::Diff> diff_cache;
// GitObjectCache objcache;
bool remote_changed = false;
ServerType server_type = ServerType::Standard;
GitHubRepositoryInfo github;
QString head_id;
bool force_fetch = false;
RepositoryItem temp_repo_for_clone_complete;
QVariant pty_process_completion_data;
};
Private1 *m1;
struct Private2;
Private2 *m2;
struct ObjectData {
QString id;
QString path;
Git::SubmoduleItem submod;
Git::CommitItem submod_commit;
QString header;
int idiff;
};
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow() override;
RepositoryWrapperFrame *frame();
RepositoryWrapperFrame const *frame() const;
QPixmap const &digitsPixmap() const;
// QString currentWorkingCopyDir() const override;
QColor color(unsigned int i);
bool isOnlineMode() const;
private:
Ui::MainWindow *ui;
void postEvent(QObject *receiver, QEvent *event, int ms_later);
void postUserFunctionEvent(const std::function<void (const QVariant &, void *)> &fn, QVariant const &v = QVariant(), void *p = nullptr, int ms_later = 0);
void updateFilesList(RepositoryWrapperFrame *frame, QString id, bool wait);
void updateFilesList(RepositoryWrapperFrame *frame, Git::CommitItem const &commit, bool wait);
void updateRepositoriesList();
void openRepository_(GitPtr g, bool keep_selection = false);
void openRepository_(RepositoryWrapperFrame *frame, GitPtr g, bool keep_selection = false);
// void prepareLogTableWidget(RepositoryWrapperFrame *frame);
QStringList selectedFiles_(QListWidget *listwidget) const;
QStringList selectedFiles() const;
void for_each_selected_files(std::function<void (QString const &)> const &fn);
void showFileList(FilesListType files_list_type);
void clearLog(RepositoryWrapperFrame *frame);
void clearFileList(RepositoryWrapperFrame *frame);
void clearDiffView(RepositoryWrapperFrame *frame);
void clearRepositoryInfo();
int repositoryIndex_(const QTreeWidgetItem *item) const;
RepositoryItem const *repositoryItem(const QTreeWidgetItem *item) const;
QTreeWidgetItem *newQTreeWidgetFolderItem(QString const &name);
void buildRepoTree(QString const &group, QTreeWidgetItem *item, QList<RepositoryItem> *repos);
void refrectRepositories();
void updateDiffView(RepositoryWrapperFrame *frame, QListWidgetItem *item);
void updateDiffView(RepositoryWrapperFrame *frame);
void updateUnstagedFileCurrentItem(RepositoryWrapperFrame *frame);
void updateStagedFileCurrentItem(RepositoryWrapperFrame *frame);
void updateStatusBarText(RepositoryWrapperFrame *frame);
void setRepositoryInfo(QString const &reponame, QString const &brname);
int indexOfRepository(const QTreeWidgetItem *treeitem) const;
void clearRepoFilter();
void appendCharToRepoFilter(ushort c);
void backspaceRepoFilter();
void revertCommit(RepositoryWrapperFrame *frame);
- void mergeBranch(const QString &commit, Git::MergeFastForward ff);
- void mergeBranch(Git::CommitItem const *commit, Git::MergeFastForward ff);
+ void mergeBranch(const QString &commit, Git::MergeFastForward ff, bool squash);
+ void mergeBranch(Git::CommitItem const *commit, Git::MergeFastForward ff, bool squash);
void rebaseBranch(Git::CommitItem const *commit);
void cherrypick(Git::CommitItem const *commit);
void merge(RepositoryWrapperFrame *frame, const Git::CommitItem *commit = nullptr);
void detectGitServerType(const GitPtr &g);
void setRemoteOnline(bool f, bool save);
void startTimers();
void onCloneCompleted(bool success, const QVariant &userdata);
void setNetworkingCommandsEnabled(bool enabled);
void blame(QListWidgetItem *item);
void blame();
QListWidgetItem *currentFileItem() const;
void execAreYouSureYouWantToContinueConnectingDialog();
void deleteRemoteBranch(RepositoryWrapperFrame *frame, Git::CommitItem const *commit);
QStringList remoteBranches(RepositoryWrapperFrame *frame, QString const &id, QStringList *all);
bool isUninitialized();
void doLogCurrentItemChanged(RepositoryWrapperFrame *frame);
void findNext(RepositoryWrapperFrame *frame);
void findText(const QString &text);
void showStatus();
void onStartEvent();
void showLogWindow(bool show);
bool isValidRemoteURL(const QString &url, const QString &sshkey);
QStringList whichCommand_(const QString &cmdfile1, const QString &cmdfile2 = {});
QString selectCommand_(const QString &cmdname, const QStringList &cmdfiles, const QStringList &list, QString path, const std::function<void (const QString &)> &callback);
QString selectCommand_(const QString &cmdname, const QString &cmdfile, const QStringList &list, const QString &path, const std::function<void (const QString &)> &callback);
const RepositoryItem *findRegisteredRepository(QString *workdir) const;
static bool git_callback(void *cookie, const char *ptr, int len);
bool execSetGlobalUserDialog();
void revertAllFiles();
void addWorkingCopyDir(QString dir, QString name, bool open);
bool execWelcomeWizardDialog();
void execRepositoryPropertyDialog(const RepositoryItem &repo, bool open_repository_menu = false);
void execSetUserDialog(const Git::User &global_user, const Git::User &repo_user, const QString &reponame);
void setGitCommand(QString path, bool save);
void setFileCommand(QString path, bool save);
void setGpgCommand(QString path, bool save);
void setSshCommand(QString path, bool save);
bool checkGitCommand();
bool checkFileCommand();
bool saveBlobAs(RepositoryWrapperFrame *frame, const QString &id, const QString &dstpath);
bool saveByteArrayAs(const QByteArray &ba, const QString &dstpath);
static QString makeRepositoryName(const QString &loc);
bool saveFileAs(const QString &srcpath, const QString &dstpath);
QString saveAsTemp(RepositoryWrapperFrame *frame, const QString &id);
QString executableOrEmpty(const QString &path);
bool checkExecutable(const QString &path);
void internalSaveCommandPath(const QString &path, bool save, const QString &name);
void logGitVersion();
void internalClearRepositoryInfo();
void checkUser();
void openRepository(bool validate, bool waitcursor = true, bool keep_selection = false);
void updateRepository();
void reopenRepository(bool log, const std::function<void (const GitPtr &)> &callback);
void setCurrentRepository(const RepositoryItem &repo, bool clear_authentication);
void openSelectedRepository();
QList<Git::Diff> makeDiffs(RepositoryWrapperFrame *frame, QString id, bool *ok);
void queryBranches(RepositoryWrapperFrame *frame, const GitPtr &g);
void updateRemoteInfo();
void queryRemotes(const GitPtr &g);
void clone(QString url = {}, QString dir = {});
void submodule_add(QString url = {}, QString local_dir = {});
const Git::CommitItem *selectedCommitItem(RepositoryWrapperFrame *frame) const;
void commit(RepositoryWrapperFrame *frame, bool amend = false);
void commitAmend(RepositoryWrapperFrame *framae);
void pushSetUpstream(const QString &remote, const QString &branch);
bool pushSetUpstream(bool testonly);
void push();
void deleteBranch(RepositoryWrapperFrame *frame, const Git::CommitItem *commit);
void deleteBranch(RepositoryWrapperFrame *frame);
void resetFile(const QStringList &paths);
void clearAuthentication();
void clearSshAuthentication();
void internalDeleteTags(const QStringList &tagnames);
bool internalAddTag(RepositoryWrapperFrame *frame, const QString &name);
void createRepository(const QString &dir);
void setLogEnabled(const GitPtr &g, bool f);
void doGitCommand(const std::function<void (GitPtr)> &callback);
void setWindowTitle_(const Git::User &user);
void setUnknownRepositoryInfo();
void setCurrentRemoteName(const QString &name);
void deleteTags(RepositoryWrapperFrame *frame, const Git::CommitItem &commit);
bool isAvatarEnabled() const;
bool isGitHub() const;
QStringList remotes() const;
QList<Git::Branch> findBranch(RepositoryWrapperFrame *frame, const QString &id);
QString tempfileHeader() const;
void deleteTempFiles();
QString getCommitIdFromTag(RepositoryWrapperFrame *frame, const QString &tag);
QString newTempFilePath();
int limitLogCount() const;
Git::Object cat_file_(RepositoryWrapperFrame *frame, const GitPtr &g, const QString &id);
bool isThereUncommitedChanges() const;
static void addDiffItems(const QList<Git::Diff> *diff_list, const std::function<void (const ObjectData &)> &add_item);
Git::CommitItemList retrieveCommitLog(const GitPtr &g);
std::map<QString, QList<Git::Branch> > &branchMapRef(RepositoryWrapperFrame *frame);
void updateCommitLogTableLater(RepositoryWrapperFrame *frame, int ms_later);
void updateWindowTitle(const GitPtr &g);
QString makeCommitInfoText(RepositoryWrapperFrame *frame, int row, QList<BranchLabel> *label_list);
void removeRepositoryFromBookmark(int index, bool ask);
void openTerminal(const RepositoryItem *repo);
void openExplorer(const RepositoryItem *repo);
bool askAreYouSureYouWantToRun(const QString &title, const QString &command);
bool editFile(const QString &path, const QString &title);
void setAppSettings(const ApplicationSettings &appsettings);
QIcon getRepositoryIcon() const;
QIcon getFolderIcon() const;
QIcon getSignatureGoodIcon() const;
QIcon getSignatureDubiousIcon() const;
QIcon getSignatureBadIcon() const;
QPixmap getTransparentPixmap() const;
QStringList findGitObject(const QString &id) const;
void writeLog(const char *ptr, int len);
void writeLog(const QString &str);
QList<BranchLabel> sortedLabels(RepositoryWrapperFrame *frame, int row) const;
void saveApplicationSettings();
void loadApplicationSettings();
void setDiffResult(const QList<Git::Diff> &diffs);
const QList<Git::SubmoduleItem> &submodules() const;
void setSubmodules(const QList<Git::SubmoduleItem> &submodules);
bool runOnRepositoryDir(const std::function<void (QString)> &callback, const RepositoryItem *repo);
NamedCommitList namedCommitItems(RepositoryWrapperFrame *frame, int flags);
static QString getObjectID(QListWidgetItem *item);
static QString getFilePath(QListWidgetItem *item);
static QString getSubmodulePath(QListWidgetItem *item);
static bool isGroupItem(QTreeWidgetItem *item);
static int indexOfLog(QListWidgetItem *item);
static int indexOfDiff(QListWidgetItem *item);
// static int getHunkIndex(QListWidgetItem *item);
static void updateSubmodules(GitPtr g, const QString &id, QList<Git::SubmoduleItem> *out);
void saveRepositoryBookmark(RepositoryItem item);
int rowFromCommitId(RepositoryWrapperFrame *frame, const QString &id);
QList<Git::Tag> findTag(RepositoryWrapperFrame *frame, const QString &id);
void sshSetPassphrase(const std::string &user, const std::string &pass);
std::string sshPassphraseUser() const;
std::string sshPassphrasePass() const;
void httpSetAuthentication(const std::string &user, const std::string &pass);
std::string httpAuthenticationUser() const;
std::string httpAuthenticationPass() const;
// const Git::CommitItemList &getLogs() const;
const Git::CommitItem *getLog(RepositoryWrapperFrame const *frame, int index) const;
void updateCommitGraph(RepositoryWrapperFrame *frame);
void initNetworking();
bool saveRepositoryBookmarks() const;
QString getBookmarksFilePath() const;
void stopPtyProcess();
void abortPtyProcess();
Git::CommitItemList *getLogsPtr(RepositoryWrapperFrame *frame);
void setLogs(RepositoryWrapperFrame *frame, const Git::CommitItemList &logs);
void clearLogs(RepositoryWrapperFrame *frame);
PtyProcess *getPtyProcess();
bool getPtyProcessOk() const;
BasicMainWindow::PtyCondition getPtyCondition();
void setPtyUserData(const QVariant &userdata);
void setPtyProcessOk(bool pty_process_ok);
bool fetch(const GitPtr &g, bool prune);
bool fetch_tags_f(const GitPtr &g);
void setPtyCondition(const PtyCondition &ptyCondition);
const QList<RepositoryItem> &getRepos() const;
QList<RepositoryItem> *getReposPtr();
AvatarLoader *getAvatarLoader();
const AvatarLoader *getAvatarLoader() const;
// int *ptrUpdateFilesListCounter();
// int *ptrUpdateCommitTableCounter();
bool interactionCanceled() const;
void setInteractionCanceled(bool canceled);
BasicMainWindow::InteractionMode interactionMode() const;
void setInteractionMode(const InteractionMode &im);
QString getRepositoryFilterText() const;
void setRepositoryFilterText(const QString &text);
void setUncommitedChanges(bool uncommited_changes);
QList<Git::Diff> *diffResult();
std::map<QString, Git::Diff> *getDiffCacheMap(RepositoryWrapperFrame *frame);
bool getRemoteChanged() const;
void setRemoteChanged(bool remote_changed);
void setServerType(const ServerType &server_type);
GitHubRepositoryInfo *ptrGitHub();
std::map<int, QList<BranchLabel> > *getLabelMap(RepositoryWrapperFrame *frame);
const std::map<int, QList<BranchLabel> > *getLabelMap(const RepositoryWrapperFrame *frame) const;
void clearLabelMap(RepositoryWrapperFrame *frame);
GitObjectCache *getObjCache(RepositoryWrapperFrame *frame);
bool getForceFetch() const;
void setForceFetch(bool force_fetch);
std::map<QString, QList<Git::Tag> > *ptrTagMap(RepositoryWrapperFrame *frame);
QString getHeadId() const;
void setHeadId(const QString &head_id);
void setPtyProcessCompletionData(const QVariant &value);
const QVariant &getTempRepoForCloneCompleteV() const;
void msgNoRepositorySelected();
bool isRepositoryOpened() const;
static std::pair<QString, QString> makeFileItemText(const ObjectData &data);
QString gitCommand() const;
QPixmap getTransparentPixmap();
static QListWidgetItem *NewListWidgetFileItem(const MainWindow::ObjectData &data);
void cancelPendingUserEvents();
protected:
void customEvent(QEvent *) override;
void dragEnterEvent(QDragEnterEvent *event) override;
// void timerEvent(QTimerEvent *) override;
void keyPressEvent(QKeyEvent *event) override;
bool event(QEvent *event) override;
bool eventFilter(QObject *watched, QEvent *event) override;
public:
void drawDigit(QPainter *pr, int x, int y, int n) const;
int digitWidth() const;
int digitHeight() const;
void setStatusBarText(QString const &text);
void clearStatusBarText();
void setCurrentLogRow(RepositoryWrapperFrame *frame, int row);
bool shown();
void deleteTags(RepositoryWrapperFrame *frame, QStringList const &tagnames);
bool addTag(RepositoryWrapperFrame *frame, QString const &name);
void updateCurrentFilesList(RepositoryWrapperFrame *frame);
void postOpenRepositoryFromGitHub(const QString &username, const QString &reponame);
int selectedLogIndex(RepositoryWrapperFrame *frame) const;
void updateAncestorCommitMap(RepositoryWrapperFrame *frame);
bool isAncestorCommit(const QString &id);
void postStartEvent(int ms_later);
void setShowLabels(bool show, bool save);
bool isLabelsVisible() const;
void updateFilesList2(RepositoryWrapperFrame *frame, const QString &id, QList<Git::Diff> *diff_list, QListWidget *listwidget);
void execCommitViewWindow(const Git::CommitItem *commit);
void execCommitPropertyDialog(QWidget *parent, RepositoryWrapperFrame *frame, const Git::CommitItem *commit);
void execCommitExploreWindow(RepositoryWrapperFrame *frame, QWidget *parent, const Git::CommitItem *commit);
void execFileHistory(const QString &path);
void execFileHistory(QListWidgetItem *item);
void showObjectProperty(QListWidgetItem *item);
bool testRemoteRepositoryValidity(const QString &url, const QString &sshkey);
QString selectGitCommand(bool save);
QString selectFileCommand(bool save);
QString selectGpgCommand(bool save);
QString selectSshCommand(bool save);
const Git::Branch ¤tBranch() const;
void setCurrentBranch(const Git::Branch &b);
const RepositoryItem ¤tRepository() const;
QString currentRepositoryName() const;
QString currentRemoteName() const;
QString currentBranchName() const;
GitPtr git(const QString &dir, const QString &submodpath, const QString &sshkey) const;
GitPtr git();
GitPtr git(Git::SubmoduleItem const &submodpath);
void autoOpenRepository(QString dir);
bool queryCommit(const QString &id, Git::CommitItem *out);
void checkout(RepositoryWrapperFrame *frame, QWidget *parent, const Git::CommitItem *commit, std::function<void ()> accepted_callback = {});
void checkout(RepositoryWrapperFrame *frame);
void jumpToCommit(RepositoryWrapperFrame *frame, QString id);
Git::Object cat_file(RepositoryWrapperFrame *frame, const QString &id);
void addWorkingCopyDir(const QString &dir, bool open);
bool saveAs(RepositoryWrapperFrame *frame, const QString &id, const QString &dstpath);
QString determinFileType_(const QString &path, bool mime, const std::function<void (const QString &, QByteArray *)> &callback) const;
QString determinFileType(const QString &path, bool mime);
QString determinFileType(QByteArray in, bool mime);
QList<Git::Tag> queryTagList(RepositoryWrapperFrame *frame);
TextEditorThemePtr themeForTextEditor();
bool isValidWorkingCopy(const GitPtr &g) const;
void emitWriteLog(const QByteArray &ba);
QString findFileID(RepositoryWrapperFrame *frame, const QString &commit_id, const QString &file);
const Git::CommitItem *commitItem(RepositoryWrapperFrame *frame, int row) const;
QIcon committerIcon(RepositoryWrapperFrame *frame, int row) const;
void changeSshKey(const QString &localdir, const QString &sshkey);
static QString abbrevCommitID(const Git::CommitItem &commit);
const Git::CommitItemList &getLogs(RepositoryWrapperFrame const *frame) const;
const QList<BranchLabel> *label(const RepositoryWrapperFrame *frame, int row) const;
ApplicationSettings *appsettings();
const ApplicationSettings *appsettings() const;
QString defaultWorkingDir() const;
WebContext *webContext();
QIcon verifiedIcon(char s) const;
QAction *addMenuActionProperty(QMenu *menu);
QString currentWorkingCopyDir() const;
Git::SubmoduleItem const *querySubmoduleByPath(const QString &path, Git::CommitItem *commit);
public slots:
void writeLog_(QByteArray ba);
private slots:
void updateUI();
void onLogVisibilityChanged();
void onPtyProcessCompleted(bool ok, const QVariant &userdata);
void onRepositoriesTreeDropped();
void on_action_about_triggered();
void on_action_clean_df_triggered();
void on_action_clone_triggered();
void on_action_commit_triggered();
void on_action_create_a_repository_triggered();
void on_action_delete_branch_triggered();
void on_action_delete_remote_branch_triggered();
void on_action_edit_git_config_triggered();
void on_action_edit_gitignore_triggered();
void on_action_edit_global_gitconfig_triggered();
void on_action_edit_settings_triggered();
void on_action_edit_tags_triggered();
void on_action_exit_triggered();
void on_action_explorer_triggered();
void on_action_fetch_triggered();
void on_action_fetch_prune_triggered();
void on_action_find_next_triggered();
void on_action_find_triggered();
void on_action_offline_triggered();
void on_action_online_triggered();
void on_action_open_existing_working_copy_triggered();
void on_action_pull_triggered();
void on_action_push_all_tags_triggered();
void on_action_push_triggered();
void on_action_push_u_triggered();
void on_action_reflog_triggered();
void on_action_repo_checkout_triggered();
void on_action_repo_jump_to_head_triggered();
void on_action_repo_jump_triggered();
void on_action_repositories_panel_triggered();
void on_action_repository_property_triggered();
void on_action_repository_status_triggered();
void on_action_reset_HEAD_1_triggered();
void on_action_reset_hard_triggered();
void on_action_set_config_user_triggered();
void on_action_set_gpg_signing_triggered();
void on_action_stash_apply_triggered();
void on_action_stash_drop_triggered();
void on_action_stash_triggered();
void on_action_stop_process_triggered();
void on_action_terminal_triggered();
void on_action_view_refresh_triggered();
void on_action_window_log_triggered(bool checked);
void on_horizontalScrollBar_log_valueChanged(int);
void on_lineEdit_filter_textChanged(QString const &text);
void on_listWidget_files_currentRowChanged(int currentRow);
void on_listWidget_files_customContextMenuRequested(const QPoint &pos);
void on_listWidget_files_itemDoubleClicked(QListWidgetItem *item);
void on_listWidget_staged_currentRowChanged(int currentRow);
void on_listWidget_staged_customContextMenuRequested(const QPoint &pos);
void on_listWidget_staged_itemDoubleClicked(QListWidgetItem *item);
void on_listWidget_unstaged_currentRowChanged(int currentRow);
void on_listWidget_unstaged_customContextMenuRequested(const QPoint &pos);
void on_listWidget_unstaged_itemDoubleClicked(QListWidgetItem *item);
void on_radioButton_remote_offline_clicked();
void on_radioButton_remote_online_clicked();
void on_tableWidget_log_currentItemChanged(QTableWidgetItem *current, QTableWidgetItem *previous);
void on_tableWidget_log_customContextMenuRequested(const QPoint &pos);
void on_tableWidget_log_itemDoubleClicked(QTableWidgetItem *);
void on_toolButton_clone_clicked();
void on_toolButton_commit_clicked();
void on_toolButton_erase_filter_clicked();
void on_toolButton_explorer_clicked();
void on_toolButton_fetch_clicked();
void on_toolButton_pull_clicked();
void on_toolButton_push_clicked();
void on_toolButton_select_all_clicked();
void on_toolButton_stage_clicked();
void on_toolButton_status_clicked();
void on_toolButton_stop_process_clicked();
void on_toolButton_terminal_clicked();
void on_toolButton_unstage_clicked();
void on_treeWidget_repos_currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous);
void on_treeWidget_repos_customContextMenuRequested(const QPoint &pos);
void on_treeWidget_repos_itemDoubleClicked(QTreeWidgetItem *item, int column);
void on_verticalScrollBar_log_valueChanged(int);
void on_action_repo_merge_triggered();
void on_action_expand_commit_log_triggered();
void on_action_expand_file_list_triggered();
void on_action_expand_diff_view_triggered();
// void on_action_wide_triggered();
void on_action_sidebar_triggered();
void on_action_show_labels_triggered();
void on_action_submodule_add_triggered();
void on_action_submodules_triggered();
void on_action_submodule_update_triggered();
void onAvatarUpdated(RepositoryWrapperFrameP frame);
void test();
void onInterval10ms();
protected:
void closeEvent(QCloseEvent *event) override;
void internalWriteLog(const char *ptr, int len);
RepositoryItem const *selectedRepositoryItem() const;
void removeSelectedRepositoryFromBookmark(bool ask);
protected slots:
void onLogIdle();
signals:
void signalWriteLog(QByteArray ba);
void remoteInfoChanged();
void signalSetRemoteChanged(bool f);
void onEscapeKeyPressed();
void updateButton();
};
#endif // MAINWINDOW_H
diff --git a/src/MergeDialog.cpp b/src/MergeDialog.cpp
index 64da9ad..0c215cc 100644
--- a/src/MergeDialog.cpp
+++ b/src/MergeDialog.cpp
@@ -1,73 +1,77 @@
#include "MergeDialog.h"
#include "ui_MergeDialog.h"
MergeDialog::MergeDialog(QString const &fastforward, const std::vector<QString> &labels, const QString curr_branch_name, QWidget *parent)
: QDialog(parent)
, ui(new Ui::MergeDialog)
{
ui->setupUi(this);
Qt::WindowFlags flags = windowFlags();
flags &= ~Qt::WindowContextHelpButtonHint;
setWindowFlags(flags);
setFastForwardPolicy(fastforward);
int select = 0;
for (size_t i = 0; i < labels.size(); i++) {
QString const &label = labels[i];
ui->listWidget_from->addItem(label);
if (label == curr_branch_name) {
select = i;
}
}
ui->listWidget_from->setCurrentRow(select);
}
MergeDialog::~MergeDialog()
{
delete ui;
}
QString MergeDialog::getFastForwardPolicy() const
{
if (ui->radioButton_ff_only->isChecked()) return "only";
if (ui->radioButton_ff_no->isChecked()) return "no";
return "default";
}
void MergeDialog::setFastForwardPolicy(QString const &ff)
{
if (ff.compare("only", Qt::CaseInsensitive) == 0) {
ui->radioButton_ff_only->click();
return;
}
if (ff.compare("no", Qt::CaseInsensitive) == 0) {
ui->radioButton_ff_no->click();
return;
}
ui->radioButton_ff_default->click();
}
Git::MergeFastForward MergeDialog::ff(QString const &ff)
{
if (ff.compare("only", Qt::CaseInsensitive) == 0) {
return Git::MergeFastForward::Only;
}
if (ff.compare("no", Qt::CaseInsensitive) == 0) {
return Git::MergeFastForward::No;
}
return Git::MergeFastForward::Default;
}
QString MergeDialog::mergeFrom() const
{
QListWidgetItem *p = ui->listWidget_from->currentItem();
return p ? p->text() : QString();
}
+bool MergeDialog::isSquashEnabled() const
+{
+ return ui->checkBox_squash->isChecked();
+}
void MergeDialog::on_listWidget_from_itemDoubleClicked(QListWidgetItem *item)
{
(void)item;
done(QDialog::Accepted);
}
diff --git a/src/MergeDialog.h b/src/MergeDialog.h
index 6408c4b..f62e8f2 100644
--- a/src/MergeDialog.h
+++ b/src/MergeDialog.h
@@ -1,30 +1,31 @@
#ifndef MERGEDIALOG_H
#define MERGEDIALOG_H
#include "Git.h"
#include <QDialog>
class QListWidgetItem;
namespace Ui {
class MergeDialog;
}
class MergeDialog : public QDialog {
Q_OBJECT
public:
explicit MergeDialog(const QString &fastforward, std::vector<QString> const &labels, QString const curr_branch_name, QWidget *parent = nullptr);
~MergeDialog();
QString getFastForwardPolicy() const;
void setFastForwardPolicy(const QString &ff);
QString mergeFrom() const;
static Git::MergeFastForward ff(const QString &ff);
+ bool isSquashEnabled() const;
private slots:
void on_listWidget_from_itemDoubleClicked(QListWidgetItem *item);
private:
Ui::MergeDialog *ui;
};
#endif // MERGEDIALOG_H
diff --git a/src/MergeDialog.ui b/src/MergeDialog.ui
index 0641f40..3af609b 100644
--- a/src/MergeDialog.ui
+++ b/src/MergeDialog.ui
@@ -1,135 +1,149 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MergeDialog</class>
<widget class="QDialog" name="MergeDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
- <width>430</width>
- <height>274</height>
+ <width>437</width>
+ <height>322</height>
</rect>
</property>
<property name="windowTitle">
<string>Merge</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Fast Forwarding</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QRadioButton" name="radioButton_ff_default">
<property name="text">
<string>Default (--ff)</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radioButton_ff_no">
<property name="text">
<string>No fast forward (--no-ff)</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radioButton_ff_only">
<property name="text">
<string>Fast forward only (--ff-only)</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
+ <item>
+ <widget class="QCheckBox" name="checkBox_squash">
+ <property name="text">
+ <string>Squash</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="Line" name="line">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>From</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QListWidget" name="listWidget_from"/>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>OK</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_2">
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<tabstops>
<tabstop>radioButton_ff_default</tabstop>
<tabstop>radioButton_ff_no</tabstop>
<tabstop>radioButton_ff_only</tabstop>
<tabstop>listWidget_from</tabstop>
<tabstop>pushButton</tabstop>
<tabstop>pushButton_2</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>pushButton</sender>
<signal>clicked()</signal>
<receiver>MergeDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>174</x>
<y>229</y>
</hint>
<hint type="destinationlabel">
<x>153</x>
<y>228</y>
</hint>
</hints>
</connection>
<connection>
<sender>pushButton_2</sender>
<signal>clicked()</signal>
<receiver>MergeDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>287</x>
<y>229</y>
</hint>
<hint type="destinationlabel">
<x>327</x>
<y>233</y>
</hint>
</hints>
</connection>
</connections>
</ui>
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Feb 7, 8:51 AM (1 d, 1 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
55930
Default Alt Text
(170 KB)
Attached To
Mode
R77 Guitar
Attached
Detach File
Event Timeline
Log In to Comment