Page MenuHomePhabricator (Chris)

No OneTemporary

Size
368 KB
Referenced Files
None
Subscribers
None
This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/src/BasicMainWindow.cpp b/src/BasicMainWindow.cpp
index 42b86d5..33e36bc 100644
--- a/src/BasicMainWindow.cpp
+++ b/src/BasicMainWindow.cpp
@@ -1,2824 +1,2813 @@
#include "ApplicationGlobal.h"
#include "BasicMainWindow.h"
#include "CheckoutDialog.h"
#include "CloneDialog.h"
#include "CommitDialog.h"
#include "CommitExploreWindow.h"
#include "CommitViewWindow.h"
#include "CreateRepositoryDialog.h"
#include "DeleteBranchDialog.h"
#include "DoYouWantToInitDialog.h"
#include "FileHistoryWindow.h"
#include "FilePropertyDialog.h"
#include "FileUtil.h"
#include "Git.h"
#include "GitDiff.h"
#include "MemoryReader.h"
#include "MySettings.h"
#include "PushDialog.h"
#include "RepositoryPropertyDialog.h"
#include "SelectCommandDialog.h"
#include "SetGlobalUserDialog.h"
#include "SetUserDialog.h"
#include "SettingsDialog.h"
#include "Terminal.h"
#include "TextEditDialog.h"
#include "UserEvent.h"
#include "WelcomeWizardDialog.h"
#include "common/joinpath.h"
#include "common/misc.h"
#include "SubmoduleAddDialog.h"
#include "gpg.h"
#include "gunzip.h"
#include "platform.h"
#include "webclient.h"
#include <QDesktopServices>
#include <QDir>
#include <QDirIterator>
#include <QEvent>
#include <QFileIconProvider>
#include <QListWidget>
#include <QMenu>
#include <QMessageBox>
#include <QStandardPaths>
#include <QTreeWidgetItem>
#include <functional>
#include <memory>
class AsyncExecGitThread_ : public QThread {
private:
GitPtr g;
std::function<void(GitPtr g)> callback;
public:
AsyncExecGitThread_(GitPtr const &g, std::function<void(GitPtr const &g)> const &callback)
: g(g)
, callback(callback)
{
}
protected:
void run() override
{
callback(g);
}
};
//
BasicMainWindow::BasicMainWindow(QWidget *parent)
: QMainWindow(parent)
// , m1(new Private1)
{
}
BasicMainWindow::~BasicMainWindow()
{
// delete m1;
}
ApplicationSettings *MainWindow::appsettings()
{
return &global->appsettings;
// return &m1->appsettings;
}
const ApplicationSettings *MainWindow::appsettings() const
{
return &global->appsettings;
// return &m1->appsettings;
}
WebContext *MainWindow::webContext()
{
return &m1->webcx;
}
QString MainWindow::gitCommand() const
{
return m1->gcx.git_command;
}
void MainWindow::autoOpenRepository(QString dir)
{
auto Open = [&](RepositoryItem const &item){
setCurrentRepository(item, true);
openRepository(false, true);
};
RepositoryItem const *repo = findRegisteredRepository(&dir);
if (repo) {
Open(*repo);
return;
}
RepositoryItem newitem;
- GitPtr g = git(dir);
+ GitPtr g = git(dir, {}, {});
if (isValidWorkingCopy(g)) {
ushort const *left = dir.utf16();
ushort const *right = left + dir.size();
if (right[-1] == '/' || right[-1] == '\\') {
right--;
}
ushort const *p = right;
while (left + 1 < p && !(p[-1] == '/' || p[-1] == '\\')) p--;
if (p < right) {
newitem.local_dir = dir;
newitem.name = QString::fromUtf16(p, right - p);
saveRepositoryBookmark(newitem);
Open(newitem);
return;
}
} else {
DoYouWantToInitDialog dlg(this, dir);
if (dlg.exec() == QDialog::Accepted) {
createRepository(dir);
}
}
}
-GitPtr MainWindow::git(QString const &dir, QString const &sshkey) const
+GitPtr MainWindow::git(QString const &dir, QString const &submodpath, QString const &sshkey) const
{
- GitPtr g = std::make_shared<Git>(m1->gcx, dir, sshkey);
+ GitPtr g = std::make_shared<Git>(m1->gcx, dir, submodpath, sshkey);
if (g && QFileInfo(g->gitCommand()).isExecutable()) {
g->setLogCallback(git_callback, (void *)this);
return g;
} else {
QString text = tr("git command not specified") + '\n';
const_cast<MainWindow *>(this)->writeLog(text);
return GitPtr();
}
}
GitPtr MainWindow::git()
{
RepositoryItem const &item = currentRepository();
- return git(item.local_dir, item.ssh_key);
+ return git(item.local_dir, {}, item.ssh_key);
}
+
GitPtr MainWindow::git(Git::SubmoduleItem const &submod)
{
if (!submod) return {};
RepositoryItem const &item = currentRepository();
- return git(item.local_dir / submod.path, item.ssh_key);
+ return git(item.local_dir, submod.path, item.ssh_key);
}
QPixmap MainWindow::getTransparentPixmap()
{
return m1->transparent_pixmap;
}
QIcon MainWindow::committerIcon(RepositoryWrapperFrame *frame, int row) const
{
QIcon icon;
if (isAvatarEnabled() && isOnlineMode()) {
auto const &logs = getLogs(frame);
if (row >= 0 && row < (int)logs.size()) {
Git::CommitItem const &commit = logs[row];
if (commit.email.indexOf('@') > 0) {
std::string email = commit.email.toStdString();
icon = getAvatarLoader()->fetch(frame, email, true); // from gavatar
}
}
}
return icon;
}
Git::CommitItem const *MainWindow::commitItem(RepositoryWrapperFrame *frame, int row) const
{
auto const &logs = getLogs(frame);
if (row >= 0 && row < (int)logs.size()) {
return &logs[row];
}
return nullptr;
}
QIcon MainWindow::verifiedIcon(char s) const
{
Git::SignatureGrade g = Git::evaluateSignature(s);
switch (g) {
case Git::SignatureGrade::Good:
return m1->signature_good_icon;
case Git::SignatureGrade::Bad:
return m1->signature_bad_icon;
case Git::SignatureGrade::Unknown:
case Git::SignatureGrade::Dubious:
case Git::SignatureGrade::Missing:
return m1->signature_dubious_icon;
}
return QIcon();
}
QString MainWindow::currentWorkingCopyDir() const
{
return m1->current_repo.local_dir;
}
bool MainWindow::isRepositoryOpened() const
{
return Git::isValidWorkingCopy(currentWorkingCopyDir());
}
-QList<BranchLabel> const *MainWindow::label(int row) const
+QList<BranchLabel> const *MainWindow::label(RepositoryWrapperFrame const *frame, int row) const
{
- auto it = getLabelMap()->find(row);
- if (it != getLabelMap()->end()) {
+ auto it = getLabelMap(frame)->find(row);
+ if (it != getLabelMap(frame)->end()) {
return &it->second;
}
return nullptr;
}
-QList<BranchLabel> MainWindow::sortedLabels(int row) const
+QList<BranchLabel> MainWindow::sortedLabels(RepositoryWrapperFrame *frame, int row) const
{
QList<BranchLabel> list;
- auto const *p = const_cast<MainWindow *>(this)->label(row);
+ auto const *p = const_cast<MainWindow *>(this)->label(frame, row);
if (p && !p->empty()) {
list = *p;
std::sort(list.begin(), list.end(), [](BranchLabel const &l, BranchLabel const &r){
auto Compare = [](BranchLabel const &l, BranchLabel const &r){
if (l.kind < r.kind) return -1;
if (l.kind > r.kind) return 1;
if (l.text < r.text) return -1;
if (l.text > r.text) return 1;
return 0;
};
return Compare(l, r) < 0;
});
}
return list;
}
-bool MainWindow::saveAs(QString const &id, QString const &dstpath)
+bool MainWindow::saveAs(RepositoryWrapperFrame *frame, QString const &id, QString const &dstpath)
{
if (id.startsWith(PATH_PREFIX)) {
return saveFileAs(id.mid(1), dstpath);
} else {
- return saveBlobAs(id, dstpath);
+ return saveBlobAs(frame, id, dstpath);
}
}
bool MainWindow::testRemoteRepositoryValidity(QString const &url, QString const &sshkey)
{
bool ok;
{
OverrideWaitCursor;
ok = isValidRemoteURL(url, sshkey);
}
QString pass = tr("The URL is a valid repository");
QString fail = tr("Failed to access the URL");
QString text = "%1\n\n%2";
text = text.arg(url).arg(ok ? pass : fail);
QString title = tr("Remote Repository");
if (ok) {
QMessageBox::information(this, title, text);
} else {
QMessageBox::critical(this, title, text);
}
return ok;
}
void MainWindow::addWorkingCopyDir(QString const &dir, bool open)
{
addWorkingCopyDir(dir, QString(), open);
}
bool MainWindow::queryCommit(QString const &id, Git::CommitItem *out)
{
*out = Git::CommitItem();
GitPtr g = git();
return g->queryCommit(id, out);
}
QAction *MainWindow::addMenuActionProperty(QMenu *menu)
{
return menu->addAction(tr("&Property"));
}
void MainWindow::jumpToCommit(RepositoryWrapperFrame *frame, QString id)
{
GitPtr g = git();
id = g->rev_parse(id);
if (!id.isEmpty()) {
int row = rowFromCommitId(frame, id);
setCurrentLogRow(frame, row);
}
}
void MainWindow::execCommitViewWindow(Git::CommitItem const *commit)
{
CommitViewWindow win(this, commit);
win.exec();
}
-void MainWindow::execCommitExploreWindow(QWidget *parent, Git::CommitItem const *commit)
+void MainWindow::execCommitExploreWindow(RepositoryWrapperFrame *frame, QWidget *parent, Git::CommitItem const *commit)
{
- CommitExploreWindow win(parent, this, getObjCache(), commit);
+ CommitExploreWindow win(parent, this, getObjCache(frame), commit);
win.exec();
}
void MainWindow::execFileHistory(QString const &path)
{
if (path.isEmpty()) return;
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
FileHistoryWindow dlg(this);
dlg.prepare(g, path);
dlg.exec();
}
-void MainWindow::execFilePropertyDialog(QListWidgetItem *item)
-{
- if (item) {
- QString path = getFilePath(item);
- QString id = getObjectID(item);
- FilePropertyDialog dlg(this);
- dlg.exec(this, path, id);
- }
-}
+
QString MainWindow::selectGitCommand(bool save)
{
char const *exe = GIT_COMMAND;
QString path = gitCommand();
auto fn = [&](QString const &path){
setGitCommand(path, save);
};
QStringList list = whichCommand_(exe);
#ifdef Q_OS_WIN
{
QStringList newlist;
QString suffix1 = "\\bin\\" GIT_COMMAND;
QString suffix2 = "\\cmd\\" GIT_COMMAND;
for (QString const &s : list) {
newlist.push_back(s);
auto DoIt = [&](QString const &suffix){
if (s.endsWith(suffix)) {
QString t = s.mid(0, s.size() - suffix.size());
QString t1 = t + "\\mingw64\\bin\\" GIT_COMMAND;
if (misc::isExecutable(t1)) newlist.push_back(t1);
QString t2 = t + "\\mingw\\bin\\" GIT_COMMAND;
if (misc::isExecutable(t2)) newlist.push_back(t2);
}
};
DoIt(suffix1);
DoIt(suffix2);
}
std::sort(newlist.begin(), newlist.end());
auto end = std::unique(newlist.begin(), newlist.end());
list.clear();
for (auto it = newlist.begin(); it != end; it++) {
list.push_back(*it);
}
}
#endif
return selectCommand_("Git", exe, list, path, fn);
}
QString MainWindow::selectFileCommand(bool save)
{
char const *exe = FILE_COMMAND;
QString path = global->file_command;
auto fn = [&](QString const &path){
setFileCommand(path, save);
};
QStringList list = whichCommand_(exe);
#ifdef Q_OS_WIN
QString dir = misc::getApplicationDir();
QString path1 = dir / FILE_COMMAND;
QString path2;
int i = dir.lastIndexOf('/');
int j = dir.lastIndexOf('\\');
if (i < j) i = j;
if (i > 0) {
path2 = dir.mid(0, i) / FILE_COMMAND;
}
path1 = misc::normalizePathSeparator(path1);
path2 = misc::normalizePathSeparator(path2);
if (misc::isExecutable(path1)) list.push_back(path1);
if (misc::isExecutable(path2)) list.push_back(path2);
#endif
return selectCommand_("File", exe, list, path, fn);
}
QString MainWindow::selectGpgCommand(bool save)
{
QString path = global->gpg_command;
auto fn = [&](QString const &path){
setGpgCommand(path, save);
};
QStringList list = whichCommand_(GPG_COMMAND, GPG2_COMMAND);
QStringList cmdlist;
cmdlist.push_back(GPG_COMMAND);
cmdlist.push_back(GPG2_COMMAND);
return selectCommand_("GPG", cmdlist, list, path, fn);
}
QString MainWindow::selectSshCommand(bool save)
{
QString path = m1->gcx.ssh_command;
auto fn = [&](QString const &path){
setSshCommand(path, save);
};
QStringList list = whichCommand_(SSH_COMMAND);
QStringList cmdlist;
cmdlist.push_back(SSH_COMMAND);
return selectCommand_("ssh", cmdlist, list, path, fn);
}
Git::Branch const &MainWindow::currentBranch() const
{
return m1->current_branch;
}
void MainWindow::setCurrentBranch(Git::Branch const &b)
{
m1->current_branch = b;
}
RepositoryItem const &MainWindow::currentRepository() const
{
return m1->current_repo;
}
QString MainWindow::currentRepositoryName() const
{
return currentRepository().name;
}
QString MainWindow::currentRemoteName() const
{
return m1->current_remote_name;
}
QString MainWindow::currentBranchName() const
{
return currentBranch().name;
}
bool MainWindow::isValidWorkingCopy(const GitPtr &g) const
{
return g && g->isValidWorkingCopy();
}
QString MainWindow::determinFileType_(QString const &path, bool mime, std::function<void (QString const &, QByteArray *)> const &callback) const
{
const_cast<MainWindow *>(this)->checkFileCommand();
return misc::determinFileType(global->file_command, path, mime, callback);
}
QString MainWindow::determinFileType(QString const &path, bool mime)
{
return determinFileType_(path, mime, [](QString const &cmd, QByteArray *ba){
misc2::runCommand(cmd, ba);
});
}
QString MainWindow::determinFileType(QByteArray in, bool mime)
{
if (in.isEmpty()) return QString();
if (in.size() > 10) {
if (memcmp(in.data(), "\x1f\x8b\x08", 3) == 0) { // gzip
QBuffer buf;
MemoryReader reader(in.data(), in.size());
reader.open(MemoryReader::ReadOnly);
buf.open(QBuffer::WriteOnly);
gunzip z;
z.set_maximul_size(100000);
z.decode(&reader, &buf);
in = buf.buffer();
}
}
// ファイル名を "-" にすると、リダイレクトで標準入力へ流し込める。
return determinFileType_("-", mime, [&](QString const &cmd, QByteArray *ba){
int r = misc2::runCommand(cmd, &in, ba);
if (r != 0) {
ba->clear();
}
});
}
QList<Git::Tag> MainWindow::queryTagList(RepositoryWrapperFrame *frame)
{
QList<Git::Tag> list;
Git::CommitItem const *commit = selectedCommitItem(frame);
if (commit && Git::isValidID(commit->commit_id)) {
- list = findTag(commit->commit_id);
+ list = findTag(frame, commit->commit_id);
}
return list;
}
int MainWindow::limitLogCount() const
{
int n = appsettings()->maximum_number_of_commit_item_acquisitions;
return (n >= 1 && n <= 100000) ? n : 10000;
}
TextEditorThemePtr MainWindow::themeForTextEditor()
{
return global->theme->text_editor_theme;
}
-Git::Object MainWindow::cat_file_(GitPtr const &g, QString const &id)
+Git::Object MainWindow::cat_file_(RepositoryWrapperFrame *frame, GitPtr const &g, QString const &id)
{
if (isValidWorkingCopy(g)) {
QString path_prefix = PATH_PREFIX;
if (id.startsWith(path_prefix)) {
- QString path = g->workingRepositoryDir();
+ QString path = g->workingDir();
path = path / id.mid(path_prefix.size());
QFile file(path);
if (file.open(QFile::ReadOnly)) {
Git::Object obj;
obj.content = file.readAll();
return obj;
}
} else if (Git::isValidID(id)) {
- return getObjCache()->catFile(id);;
+ return getObjCache(frame)->catFile(id);;
}
}
return Git::Object();
}
-Git::Object MainWindow::cat_file(QString const &id)
+Git::Object MainWindow::cat_file(RepositoryWrapperFrame *frame, QString const &id)
{
- return cat_file_(git(), id);
+ return cat_file_(frame, git(), id);
}
QString MainWindow::newTempFilePath()
{
QString tmpdir = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
QString path = tmpdir / tempfileHeader() + QString::number(m1->temp_file_counter);
m1->temp_file_counter++;
return path;
}
-QString MainWindow::findFileID(QString const &commit_id, QString const &file)
+QString MainWindow::findFileID(RepositoryWrapperFrame *frame, QString const &commit_id, QString const &file)
{
- return lookupFileID(getObjCache(), commit_id, file);
+ return lookupFileID(getObjCache(frame), commit_id, file);
}
void MainWindow::setAppSettings(const ApplicationSettings &appsettings)
{
global->appsettings = appsettings;
// m1->appsettings = appsettings;
}
QPixmap MainWindow::getTransparentPixmap() const
{
return m1->transparent_pixmap;
}
QIcon MainWindow::getSignatureBadIcon() const
{
return m1->signature_bad_icon;
}
QIcon MainWindow::getSignatureDubiousIcon() const
{
return m1->signature_dubious_icon;
}
QIcon MainWindow::getSignatureGoodIcon() const
{
return m1->signature_good_icon;
}
QIcon MainWindow::getFolderIcon() const
{
return m1->folder_icon;
}
QIcon MainWindow::getRepositoryIcon() const
{
return m1->repository_icon;
}
BasicMainWindow::PtyCondition MainWindow::getPtyCondition()
{
return m1->pty_condition;
}
void MainWindow::setPtyUserData(QVariant const &userdata)
{
m1->pty_process.setVariant(userdata);
}
void MainWindow::setPtyCondition(const PtyCondition &ptyCondition)
{
m1->pty_condition = ptyCondition;
}
PtyProcess *MainWindow::getPtyProcess()
{
return &m1->pty_process;
}
bool MainWindow::getPtyProcessOk() const
{
return m1->pty_process_ok;
}
void MainWindow::setPtyProcessOk(bool pty_process_ok)
{
m1->pty_process_ok = pty_process_ok;
}
const QList<RepositoryItem> &MainWindow::getRepos() const
{
return m1->repos;
}
QList<RepositoryItem> *MainWindow::getReposPtr()
{
return &m1->repos;
}
void MainWindow::setCurrentRemoteName(QString const &name)
{
m1->current_remote_name = name;
}
AvatarLoader *MainWindow::getAvatarLoader()
{
return &m1->avatar_loader;
}
AvatarLoader const *MainWindow::getAvatarLoader() const
{
return &m1->avatar_loader;
}
//int *MainWindow::ptrUpdateFilesListCounter()
//{
// return &m1->update_files_list_counter;
//}
//int *MainWindow::ptrUpdateCommitTableCounter()
//{
// return &m1->update_commit_table_counter;
//}
bool MainWindow::interactionCanceled() const
{
return m1->interaction_canceled;
}
void MainWindow::setInteractionCanceled(bool canceled)
{
m1->interaction_canceled = canceled;
}
BasicMainWindow::InteractionMode MainWindow::interactionMode() const
{
return m1->interaction_mode;
}
void MainWindow::setInteractionMode(const InteractionMode &im)
{
m1->interaction_mode = im;
}
QString MainWindow::getRepositoryFilterText() const
{
return m1->repository_filter_text;
}
void MainWindow::setRepositoryFilterText(QString const &text)
{
m1->repository_filter_text = text;
}
void MainWindow::setUncommitedChanges(bool uncommited_changes)
{
m1->uncommited_changes = uncommited_changes;
}
QList<Git::Diff> *MainWindow::diffResult()
{
return &m1->diff_result;
}
void MainWindow::setDiffResult(const QList<Git::Diff> &diffs)
{
m1->diff_result = diffs;
}
const QList<Git::SubmoduleItem> &MainWindow::submodules() const
{
return m1->submodules;
}
void MainWindow::setSubmodules(const QList<Git::SubmoduleItem> &submodules)
{
m1->submodules = submodules;
}
-std::map<QString, Git::Diff> *MainWindow::getDiffCacheMap()
+std::map<QString, Git::Diff> *MainWindow::getDiffCacheMap(RepositoryWrapperFrame *frame)
{
- return &m1->diff_cache;
+// return &m1->diff_cache;
+ return &frame->diff_cache;
}
bool MainWindow::getRemoteChanged() const
{
return m1->remote_changed;
}
void MainWindow::setRemoteChanged(bool remote_changed)
{
m1->remote_changed = remote_changed;
}
void MainWindow::setServerType(const ServerType &server_type)
{
m1->server_type = server_type;
}
GitHubRepositoryInfo *MainWindow::ptrGitHub()
{
return &m1->github;
}
-std::map<int, QList<BranchLabel>> *MainWindow::getLabelMap()
+std::map<int, QList<BranchLabel>> *MainWindow::getLabelMap(RepositoryWrapperFrame *frame)
{
- return &m1->label_map;
+// return &m1->label_map;
+ return &frame->label_map;
}
-std::map<int, QList<BranchLabel>> const *MainWindow::getLabelMap() const
+std::map<int, QList<BranchLabel>> const *MainWindow::getLabelMap(RepositoryWrapperFrame const *frame) const
{
- return &m1->label_map;
+// return &m1->label_map;
+ return &frame->label_map;
}
-void MainWindow::clearLabelMap()
+void MainWindow::clearLabelMap(RepositoryWrapperFrame *frame)
{
- m1->label_map.clear();
+ frame->label_map.clear();
+// m1->label_map.clear();
}
-GitObjectCache *MainWindow::getObjCache()
+GitObjectCache *MainWindow::getObjCache(RepositoryWrapperFrame *frame)
{
- return &m1->objcache;
+ return &frame->objcache;
+// return &m1->objcache;
}
bool MainWindow::getForceFetch() const
{
return m1->force_fetch;
}
void MainWindow::setForceFetch(bool force_fetch)
{
m1->force_fetch = force_fetch;
}
-std::map<QString, QList<Git::Tag>> *MainWindow::ptrTagMap()
+std::map<QString, QList<Git::Tag>> *MainWindow::ptrTagMap(RepositoryWrapperFrame *frame)
{
- return &m1->tag_map;
+// return &m1->tag_map;
+ return &frame->tag_map;
}
QString MainWindow::getHeadId() const
{
return m1->head_id;
}
void MainWindow::setHeadId(QString const &head_id)
{
m1->head_id = head_id;
}
void MainWindow::setPtyProcessCompletionData(QVariant const &value)
{
m1->pty_process_completion_data = value;
}
QVariant const &MainWindow::getTempRepoForCloneCompleteV() const
{
return m1->pty_process_completion_data;
}
void MainWindow::updateCommitGraph(RepositoryWrapperFrame *frame)
{
auto const &logs = getLogs(frame);
auto *logsp = getLogsPtr(frame);
const size_t LogCount = logs.size();
// 樹形図情報を構築する
if (LogCount > 0) {
auto LogItem = [&](size_t i)->Git::CommitItem &{ return logsp->at(i); };
enum { // 有向グラフを構築するあいだ CommitItem::marker_depth をフラグとして使用する
UNKNOWN = 0,
KNOWN = 1,
};
for (Git::CommitItem &item : *logsp) {
item.marker_depth = UNKNOWN;
}
// コミットハッシュを検索して、親コミットのインデックスを求める
for (size_t i = 0; i < LogCount; i++) {
Git::CommitItem *item = &LogItem(i);
item->parent_lines.clear();
if (item->parent_ids.empty()) {
item->resolved = true;
} else {
for (int j = 0; j < item->parent_ids.size(); j++) { // 親の数だけループ
QString const &parent_id = item->parent_ids[j]; // 親のハッシュ値
for (size_t k = i + 1; k < LogCount; k++) { // 親を探す
if (LogItem(k).commit_id == parent_id) { // ハッシュ値が一致したらそれが親
item->parent_lines.emplace_back(k); // インデックス値を記憶
LogItem(k).has_child = true;
LogItem(k).marker_depth = KNOWN;
item->resolved = true;
break;
}
}
}
}
}
std::vector<Element> elements; // 線分リスト
{ // 線分リストを作成する
std::deque<Task> tasks; // 未処理タスクリスト
{
for (size_t i = 0; i < LogCount; i++) {
Git::CommitItem *item = &LogItem(i);
if (item->marker_depth == UNKNOWN) {
int n = item->parent_lines.size(); // 最初のコミットアイテム
for (int j = 0; j < n; j++) {
tasks.emplace_back(i, j); // タスクを追加
}
}
item->marker_depth = UNKNOWN;
}
}
while (!tasks.empty()) { // タスクが残っているならループ
Element e;
Task task;
{ // 最初のタスクを取り出す
task = tasks.front();
tasks.pop_front();
}
e.indexes.push_back(task.index); // 先頭のインデックスを追加
int index = LogItem(task.index).parent_lines[task.parent].index; // 開始インデックス
while (index > 0 && (size_t)index < LogCount) { // 最後に到達するまでループ
e.indexes.push_back(index); // インデックスを追加
int n = LogItem(index).parent_lines.size(); // 親の数
if (n == 0) break; // 親がないなら終了
Git::CommitItem *item = &LogItem(index);
if (item->marker_depth == KNOWN) break; // 既知のアイテムに到達したら終了
item->marker_depth = KNOWN; // 既知のアイテムにする
for (int i = 1; i < n; i++) {
tasks.emplace_back(index, i); // タスク追加
}
index = LogItem(index).parent_lines[0].index; // 次の親(親リストの先頭の要素)
}
if (e.indexes.size() >= 2) {
elements.push_back(e);
}
}
}
// 線情報をクリア
for (Git::CommitItem &item : *logsp) {
item.marker_depth = -1;
item.parent_lines.clear();
}
// マークと線の深さを決める
if (!elements.empty()) {
{ // 優先順位を調整する
std::sort(elements.begin(), elements.end(), [](Element const &left, Element const &right){
int i = 0;
{ // 長いものを優先して左へ
int l = left.indexes.back() - left.indexes.front();
int r = right.indexes.back() - right.indexes.front();
i = r - l; // 降順
}
if (i == 0) {
// コミットが新しいものを優先して左へ
int l = left.indexes.front();
int r = right.indexes.front();
i = l - r; // 昇順
}
return i < 0;
});
// 子の無いブランチ(タグ等)が複数連続しているとき、古いコミットを右に寄せる
{
for (size_t i = 0; i + 1 < elements.size(); i++) {
Element *e = &elements[i];
int index1 = e->indexes.front();
if (index1 > 0 && !LogItem(index1).has_child) { // 子がない
// 新しいコミットを探す
for (size_t j = i + 1; j < elements.size(); j++) { // 現在位置より後ろを探す
Element *f = &elements[j];
int index2 = f->indexes.front();
if (index1 == index2 + 1) { // 一つだけ新しいコミット
Element t = std::move(*f);
elements.erase(elements.begin() + j); // 移動元を削除
elements.insert(elements.begin() + i, std::move(t)); // 現在位置に挿入
}
}
// 古いコミットを探す
size_t j = 0;
while (j < i) { // 現在位置より前を探す
Element *f = &elements[j];
int index2 = f->indexes.front();
if (index1 + 1 == index2) { // 一つだけ古いコミット
Element t = std::move(*f);
elements.erase(elements.begin() + j); // 移動元を削除
elements.insert(elements.begin() + i, std::move(t)); // 現在位置の次に挿入
index1 = index2;
f = e;
} else {
j++;
}
}
}
}
}
}
{ // 最初の線は深さを0にする
Element *e = &elements.front();
for (size_t i = 0; i < e->indexes.size(); i++) {
int index = e->indexes[i];
LogItem(index).marker_depth = 0; // マークの深さを設定
e->depth = 0; // 線の深さを設定
}
}
// 最初以外の線分の深さを決める
for (size_t i = 1; i < elements.size(); i++) { // 最初以外をループ
Element *e = &elements[i];
int depth = 1;
while (1) { // 失敗したら繰り返し
for (size_t j = 0; j < i; j++) { // 既に処理済みの線を調べる
Element const *f = &elements[j]; // 検査対象
if (e->indexes.size() == 2) { // 二つしかない場合
int from = e->indexes[0]; // 始点
int to = e->indexes[1]; // 終点
if (LogItem(from).has_child) {
for (size_t k = 0; k + 1 < f->indexes.size(); k++) { // 検査対象の全ての線分を調べる
int curr = f->indexes[k];
int next = f->indexes[k + 1];
if (from > curr && to == next) { // 決定済みの線に直結できるか判定
e->indexes.back() = from + 1; // 現在の一行下に直結する
e->depth = elements[j].depth; // 接続先の深さ
goto DONE; // 決定
}
}
}
}
if (depth == f->depth) { // 同じ深さ
if (e->indexes.back() > f->indexes.front() && e->indexes.front() < f->indexes.back()) { // 重なっている
goto FAIL; // この深さには線を置けないのでやりなおし
}
}
}
for (size_t j = 0; j < e->indexes.size(); j++) {
int index = e->indexes[j];
Git::CommitItem *item = &LogItem(index);
if (j == 0 && item->has_child) { // 最初のポイントで子がある場合
// nop
} else if ((j > 0 && j + 1 < e->indexes.size()) || item->marker_depth < 0) { // 最初と最後以外、または、未確定の場合
item->marker_depth = depth; // マークの深さを設定
}
}
e->depth = depth; // 深さを決定
goto DONE; // 決定
FAIL:;
depth++; // 一段深くして再挑戦
}
DONE:;
}
// 線情報を生成する
for (auto &e : elements) {
auto ColorNumber = [&](){ return e.depth; };
size_t count = e.indexes.size();
for (size_t i = 0; i + 1 < count; i++) {
int curr = e.indexes[i];
int next = e.indexes[i + 1];
TreeLine line(next, e.depth);
line.color_number = ColorNumber();
line.bend_early = (i + 2 < count || !LogItem(next).resolved);
if (i + 2 == count) {
int join = false;
if (count > 2) { // 直結ではない
join = true;
} else if (!LogItem(curr).has_child) { // 子がない
join = true;
int d = LogItem(curr).marker_depth; // 開始点の深さ
for (int j = curr + 1; j < next; j++) {
Git::CommitItem *item = &LogItem(j);
if (item->marker_depth == d) { // 衝突する
join = false; // 迂回する
break;
}
}
}
if (join) {
line.depth = LogItem(next).marker_depth; // 合流する先のアイテムの深さと同じにする
}
}
LogItem(curr).parent_lines.push_back(line); // 線を追加
}
}
} else {
if (LogCount == 1) { // コミットが一つだけ
LogItem(0).marker_depth = 0;
}
}
}
}
bool MainWindow::fetch(GitPtr const &g, bool prune)
{
setPtyCondition(PtyCondition::Fetch);
setPtyProcessOk(true);
g->fetch(getPtyProcess(), prune);
while (1) {
if (getPtyProcess()->wait(1)) break;
QApplication::processEvents();
}
return getPtyProcessOk();
}
bool MainWindow::fetch_tags_f(GitPtr const &g)
{
setPtyCondition(PtyCondition::Fetch);
setPtyProcessOk(true);
g->fetch_tags_f(getPtyProcess());
while (1) {
if (getPtyProcess()->wait(1)) break;
QApplication::processEvents();
}
return getPtyProcessOk();
}
bool MainWindow::git_callback(void *cookie, const char *ptr, int len)
{
auto *mw = (MainWindow *)cookie;
mw->emitWriteLog(QByteArray(ptr, len));
return true;
}
QString MainWindow::selectCommand_(QString const &cmdname, QStringList const &cmdfiles, QStringList const &list, QString path, std::function<void (QString const &)> const &callback)
{
QString window_title = tr("Select %1 command");
window_title = window_title.arg(cmdfiles.front());
SelectCommandDialog dlg(this, cmdname, cmdfiles, path, list);
dlg.setWindowTitle(window_title);
if (dlg.exec() == QDialog::Accepted) {
path = dlg.selectedFile();
path = misc::normalizePathSeparator(path);
QFileInfo info(path);
if (info.isExecutable()) {
callback(path);
return path;
}
}
return QString();
}
QString MainWindow::selectCommand_(QString const &cmdname, QString const &cmdfile, QStringList const &list, QString const &path, std::function<void (QString const &)> const &callback)
{
QStringList cmdfiles;
cmdfiles.push_back(cmdfile);
return selectCommand_(cmdname, cmdfiles, list, path, callback);
}
bool MainWindow::checkGitCommand()
{
while (1) {
if (misc::isExecutable(gitCommand())) {
return true;
}
if (selectGitCommand(true).isEmpty()) {
close();
return false;
}
}
}
bool MainWindow::checkFileCommand()
{
while (1) {
if (misc::isExecutable(global->file_command)) {
return true;
}
if (selectFileCommand(true).isEmpty()) {
close();
return false;
}
}
}
bool MainWindow::checkExecutable(QString const &path)
{
if (QFileInfo(path).isExecutable()) {
return true;
}
QString text = "The specified program '%1' is not executable.\n";
text = text.arg(path);
writeLog(text);
return false;
}
void MainWindow::internalSaveCommandPath(QString const &path, bool save, QString const &name)
{
if (checkExecutable(path)) {
if (save) {
MySettings s;
s.beginGroup("Global");
s.setValue(name, path);
s.endGroup();
}
}
}
QStringList MainWindow::whichCommand_(QString const &cmdfile1, QString const &cmdfile2)
{
QStringList list;
if (!cmdfile1.isEmpty()){
std::vector<std::string> vec;
FileUtil::qwhich(cmdfile1.toStdString(), &vec);
for (std::string const &s : vec) {
list.push_back(QString::fromStdString(s));
}
}
if (!cmdfile2.isEmpty()){
std::vector<std::string> vec;
FileUtil::qwhich(cmdfile2.toStdString(), &vec);
for (std::string const &s : vec) {
list.push_back(QString::fromStdString(s));
}
}
return list;
}
void MainWindow::setWindowTitle_(Git::User const &user)
{
if (user.name.isEmpty() && user.email.isEmpty()) {
setWindowTitle(qApp->applicationName());
} else {
setWindowTitle(QString("%1 : %2 <%3>")
.arg(qApp->applicationName())
.arg(user.name)
.arg(user.email)
);
}
}
bool MainWindow::execSetGlobalUserDialog()
{
SetGlobalUserDialog dlg(this);
if (dlg.exec() == QDialog::Accepted) {
GitPtr g = git();
Git::User user = dlg.user();
g->setUser(user, true);
updateWindowTitle(g);
return true;
}
return false;
}
bool MainWindow::saveByteArrayAs(const QByteArray &ba, QString const &dstpath)
{
QFile file(dstpath);
if (file.open(QFile::WriteOnly)) {
file.write(ba);
file.close();
return true;
}
QString msg = "Failed to open the file '%1' for write";
msg = msg.arg(dstpath);
qDebug() << msg;
return false;
}
bool MainWindow::saveFileAs(QString const &srcpath, QString const &dstpath)
{
QFile f(srcpath);
if (f.open(QFile::ReadOnly)) {
QByteArray ba = f.readAll();
if (saveByteArrayAs(ba, dstpath)) {
return true;
}
} else {
QString msg = "Failed to open the file '%1' for read";
msg = msg.arg(srcpath);
qDebug() << msg;
}
return false;
}
-bool MainWindow::saveBlobAs(QString const &id, QString const &dstpath)
+bool MainWindow::saveBlobAs(RepositoryWrapperFrame *frame, QString const &id, QString const &dstpath)
{
- Git::Object obj = cat_file(id);
+ Git::Object obj = cat_file(frame, id);
if (!obj.content.isEmpty()) {
if (saveByteArrayAs(obj.content, dstpath)) {
return true;
}
} else {
QString msg = "Failed to get the content of the object '%1'";
msg = msg.arg(id);
qDebug() << msg;
}
return false;
}
void MainWindow::revertAllFiles()
{
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
QString cmd = "git reset --hard HEAD";
if (askAreYouSureYouWantToRun(tr("Revert all files"), "> " + cmd)) {
g->resetAllFiles();
openRepository(true);
}
}
void MainWindow::deleteTags(RepositoryWrapperFrame *frame, Git::CommitItem const &commit)
{
- auto it = ptrTagMap()->find(commit.commit_id);
- if (it != ptrTagMap()->end()) {
+ auto it = ptrTagMap(frame)->find(commit.commit_id);
+ if (it != ptrTagMap(frame)->end()) {
QStringList names;
QList<Git::Tag> const &tags = it->second;
for (Git::Tag const &tag : tags) {
names.push_back(tag.name);
}
deleteTags(frame, names);
}
}
bool MainWindow::isAvatarEnabled() const
{
return appsettings()->get_committer_icon;
}
bool MainWindow::isGitHub() const
{
return m1->server_type == ServerType::GitHub;
}
QStringList MainWindow::remotes() const
{
return m1->remotes;
}
-QList<Git::Branch> MainWindow::findBranch(QString const &id)
+QList<Git::Branch> MainWindow::findBranch(RepositoryWrapperFrame *frame, QString const &id)
{
- auto it = branchMapRef().find(id);
- if (it != branchMapRef().end()) {
+ auto it = branchMapRef(frame).find(id);
+ if (it != branchMapRef(frame).end()) {
return it->second;
}
return QList<Git::Branch>();
}
-QString MainWindow::saveAsTemp(QString const &id)
+QString MainWindow::saveAsTemp(RepositoryWrapperFrame *frame, QString const &id)
{
QString path = newTempFilePath();
- saveAs(id, path);
+ saveAs(frame, id, path);
return path;
}
QString MainWindow::tempfileHeader() const
{
QString name = "jp_soramimi_Guitar_%1_";
return name.arg(QApplication::applicationPid());
}
void MainWindow::deleteTempFiles()
{
QString dir = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
QString name = tempfileHeader();
QDirIterator it(dir, { name + "*" });
while (it.hasNext()) {
QString path = it.next();
QFile::remove(path);
}
}
-QString MainWindow::getCommitIdFromTag(QString const &tag)
+QString MainWindow::getCommitIdFromTag(RepositoryWrapperFrame *frame, QString const &tag)
{
- return getObjCache()->getCommitIdFromTag(tag);
+ return getObjCache(frame)->getCommitIdFromTag(tag);
}
bool MainWindow::isValidRemoteURL(QString const &url, QString const &sshkey)
{
if (url.indexOf('\"') >= 0) {
return false;
}
stopPtyProcess();
- GitPtr g = git(QString(), sshkey);
+ GitPtr g = git({}, {}, sshkey);
QString cmd = "ls-remote \"%1\" HEAD";
cmd = cmd.arg(url);
bool f = g->git(cmd, false, false, getPtyProcess());
{
QTime time;
time.start();
while (!getPtyProcess()->wait(10)) {
if (time.elapsed() > 10000) {
f = false;
break;
}
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
}
stopPtyProcess();
}
if (f) {
f = (getPtyProcess()->getExitCode() == 0);
}
QString line;
{
std::vector<char> v;
getPtyProcess()->readResult(&v);
if (!v.empty()) {
line = QString::fromUtf8(&v[0], v.size()).trimmed();
}
}
if (f) {
qDebug() << "This is a valid repository.";
int i = -1;
for (int j = 0; j < line.size(); j++) {
ushort c = line.utf16()[j];
if (QChar(c).isSpace()) {
i = j;
break;
}
}
QString head;
if (i == GIT_ID_LENGTH) {
QString id = line.mid(0, i);
QString name = line.mid(i + 1).trimmed();
qDebug() << id << name;
if (name == "HEAD" && Git::isValidID(id)) {
head = id;
}
}
if (head.isEmpty()) {
qDebug() << "But HEAD not found";
}
return true;
}
qDebug() << "This is not a repository.";
return false;
}
-QString MainWindow::getObjectID(QListWidgetItem *item)
-{
- int i = indexOfDiff(item);
- if (i >= 0 && i < diffResult()->size()) {
- Git::Diff const &diff = diffResult()->at(i);
- return diff.blob.a_id;
- }
- return QString();
-}
+
void MainWindow::addWorkingCopyDir(QString dir, QString name, bool open)
{
if (dir.endsWith(".git")) {
int i = dir.size();
if (i > 4) {
ushort c = dir.utf16()[i - 5];
if (c == '/' || c == '\\') {
dir = dir.mid(0, i - 5);
}
}
}
if (!Git::isValidWorkingCopy(dir)) {
if (QFileInfo(dir).isDir()) {
QString text;
text += tr("The folder is not a valid git repository.") + '\n';
text += '\n';
text += dir + '\n';
text += '\n';
text += tr("Do you want to initialize it as a git repository ?") + '\n';
int r = QMessageBox::information(this, tr("Initialize Repository") , text, QMessageBox::Yes, QMessageBox::No);
if (r == QMessageBox::Yes) {
createRepository(dir);
}
}
return;
}
if (name.isEmpty()) {
name = makeRepositoryName(dir);
}
RepositoryItem item;
item.local_dir = dir;
item.name = name;
saveRepositoryBookmark(item);
if (open) {
setCurrentRepository(item, true);
- GitPtr g = git(item.local_dir);
+ GitPtr g = git(item.local_dir, {}, {});
openRepository_(g);
}
}
QString MainWindow::makeRepositoryName(QString const &loc)
{
int i = loc.lastIndexOf('/');
int j = loc.lastIndexOf('\\');
if (i < j) i = j;
if (i >= 0) {
i++;
j = loc.size();
if (loc.endsWith(".git")) {
j -= 4;
}
return loc.mid(i, j - i);
}
return QString();
}
void MainWindow::clearAuthentication()
{
clearSshAuthentication();
m1->http_uid.clear();
m1->http_pwd.clear();
}
QStringList MainWindow::findGitObject(const QString &id) const
{
QStringList list;
std::set<QString> set;
if (Git::isValidID(id)) {
{
QString a = id.mid(0, 2);
QString b = id.mid(2);
QString dir = m1->current_repo.local_dir / ".git/objects" / a;
QDirIterator it(dir);
while (it.hasNext()) {
it.next();
QFileInfo info = it.fileInfo();
if (info.isFile()) {
QString c = info.fileName();
if (c.startsWith(b)) {
set.insert(set.end(), a + c);
}
}
}
}
{
QString dir = m1->current_repo.local_dir / ".git/objects/pack";
QDirIterator it(dir);
while (it.hasNext()) {
it.next();
QFileInfo info = it.fileInfo();
if (info.isFile() && info.fileName().startsWith("pack-") && info.fileName().endsWith(".idx")) {
GitPackIdxV2 idx;
idx.parse(info.absoluteFilePath());
idx.each([&](GitPackIdxItem const *item){
if (item->id.startsWith(id)) {
set.insert(item->id);
}
return true;
});
}
}
}
for (QString const &s : set) {
list.push_back(s);
}
}
return list;
}
void MainWindow::writeLog(const char *ptr, int len)
{
internalWriteLog(ptr, len);
}
void MainWindow::writeLog(const QString &str)
{
std::string s = str.toStdString();
writeLog(s.c_str(), s.size());
}
void MainWindow::emitWriteLog(QByteArray const &ba)
{
emit signalWriteLog(ba);
}
void MainWindow::writeLog_(QByteArray ba)
{
if (!ba.isEmpty()) {
writeLog(ba.data(), ba.size());
}
}
void MainWindow::queryRemotes(GitPtr const &g)
{
if (!g) return;
m1->remotes = g->getRemotes();
std::sort(m1->remotes.begin(), m1->remotes.end());
}
void MainWindow::msgNoRepositorySelected()
{
QMessageBox::warning(this, qApp->applicationName(), tr("No repository selected"));
}
bool MainWindow::runOnRepositoryDir(std::function<void (QString)> const &callback, RepositoryItem const *repo)
{
if (!repo) {
repo = &m1->current_repo;
}
QString dir = repo->local_dir;
dir.replace('\\', '/');
if (QFileInfo(dir).isDir()) {
callback(dir);
return true;
}
msgNoRepositorySelected();
return false;
}
void MainWindow::clearSshAuthentication()
{
m1->ssh_passphrase_user.clear();
m1->ssh_passphrase_pass.clear();
}
-QString MainWindow::getFilePath(QListWidgetItem *item)
-{
- if (!item) return QString();
- return item->data(FilePathRole).toString();
-}
+
bool MainWindow::isGroupItem(QTreeWidgetItem *item)
{
if (item) {
int index = item->data(0, IndexRole).toInt();
if (index == GroupItem) {
return true;
}
}
return false;
}
int MainWindow::indexOfLog(QListWidgetItem *item)
{
if (!item) return -1;
return item->data(IndexRole).toInt();
}
int MainWindow::indexOfDiff(QListWidgetItem *item)
{
if (!item) return -1;
return item->data(DiffIndexRole).toInt();
}
-int MainWindow::getHunkIndex(QListWidgetItem *item)
-{
- if (!item) return -1;
- return item->data(HunkIndexRole).toInt();
-}
+//int MainWindow::getHunkIndex(QListWidgetItem *item)
+//{
+// if (!item) return -1;
+// return item->data(HunkIndexRole).toInt();
+//}
void MainWindow::initNetworking()
{
std::string http_proxy;
std::string https_proxy;
if (appsettings()->proxy_type == "auto") {
#ifdef Q_OS_WIN
http_proxy = misc::makeProxyServerURL(getWin32HttpProxy().toStdString());
#else
auto getienv = [](std::string const &name)->char const *{
char **p = environ;
while (*p) {
if (strncasecmp(*p, name.c_str(), name.size()) == 0) {
char const *e = *p + name.size();
if (*e == '=') {
return e + 1;
}
}
p++;
}
return nullptr;
};
char const *p;
p = getienv("http_proxy");
if (p) {
http_proxy = misc::makeProxyServerURL(std::string(p));
}
p = getienv("https_proxy");
if (p) {
https_proxy = misc::makeProxyServerURL(std::string(p));
}
#endif
} else if (appsettings()->proxy_type == "manual") {
http_proxy = appsettings()->proxy_server.toStdString();
}
webContext()->set_http_proxy(http_proxy);
webContext()->set_https_proxy(https_proxy);
}
QString MainWindow::abbrevCommitID(Git::CommitItem const &commit)
{
return commit.commit_id.mid(0, 7);
}
bool MainWindow::saveRepositoryBookmarks() const
{
QString path = getBookmarksFilePath();
return RepositoryBookmark::save(path, &getRepos());
}
QString MainWindow::getBookmarksFilePath() const
{
return global->app_config_dir / "bookmarks.xml";
}
void MainWindow::stopPtyProcess()
{
getPtyProcess()->stop();
QDir::setCurrent(m1->starting_dir);
}
void MainWindow::abortPtyProcess()
{
stopPtyProcess();
setPtyProcessOk(false);
setInteractionCanceled(true);
}
void MainWindow::saveApplicationSettings()
{
SettingsDialog::saveSettings(appsettings());
// SettingsDialog::saveSettings(&m1->appsettings);
}
void MainWindow::loadApplicationSettings()
{
SettingsDialog::loadSettings(appsettings());
}
bool MainWindow::execWelcomeWizardDialog()
{
WelcomeWizardDialog dlg(this);
dlg.set_git_command_path(appsettings()->git_command);
dlg.set_file_command_path(appsettings()->file_command);
dlg.set_default_working_folder(appsettings()->default_working_dir);
if (misc::isExecutable(appsettings()->git_command)) {
gitCommand() = appsettings()->git_command;
- Git g(m1->gcx, {}, {});
+ Git g(m1->gcx, {}, {}, {});
Git::User user = g.getUser(Git::Source::Global);
dlg.set_user_name(user.name);
dlg.set_user_email(user.email);
}
if (dlg.exec() == QDialog::Accepted) {
setGitCommand(dlg.git_command_path(), false);
setFileCommand(dlg.file_command_path(), false);
// appsettings()->git_command = dlg.git_command_path();
appsettings()->file_command = dlg.file_command_path();
// m->gcx.git_command = appsettings()->git_command;
global->file_command = appsettings()->file_command;
appsettings()->default_working_dir = dlg.default_working_folder();
saveApplicationSettings();
if (misc::isExecutable(appsettings()->git_command)) {
GitPtr g = git();
Git::User user;
user.name = dlg.user_name();
user.email = dlg.user_email();
g->setUser(user, true);
}
return true;
}
return false;
}
void MainWindow::execRepositoryPropertyDialog(RepositoryItem const &repo, bool open_repository_menu)
{
QString workdir = repo.local_dir;
if (workdir.isEmpty()) {
workdir = currentWorkingCopyDir();
}
QString name = repo.name;
if (name.isEmpty()) {
name = makeRepositoryName(workdir);
}
- GitPtr g = git(workdir, repo.ssh_key);
+ GitPtr g = git(workdir, {}, repo.ssh_key);
RepositoryPropertyDialog dlg(this, &m1->gcx, g, repo, open_repository_menu);
dlg.exec();
if (dlg.isRemoteChanged()) {
emit remoteInfoChanged();
}
}
void MainWindow::execSetUserDialog(Git::User const &global_user, Git::User const &repo_user, QString const &reponame)
{
SetUserDialog dlg(this, global_user, repo_user, reponame);
if (dlg.exec() == QDialog::Accepted) {
GitPtr g = git();
Git::User user = dlg.user();
if (dlg.isGlobalChecked()) {
g->setUser(user, true);
}
if (dlg.isRepositoryChecked()) {
g->setUser(user, false);
}
updateWindowTitle(g);
}
}
QString MainWindow::executableOrEmpty(QString const &path)
{
return checkExecutable(path) ? path : QString();
}
void MainWindow::setGitCommand(QString path, bool save)
{
appsettings()->git_command = m1->gcx.git_command = executableOrEmpty(path);
internalSaveCommandPath(path, save, "GitCommand");
}
void MainWindow::setFileCommand(QString path, bool save)
{
appsettings()->file_command = global->file_command = executableOrEmpty(path);
internalSaveCommandPath(path, save, "FileCommand");
}
void MainWindow::setGpgCommand(QString path, bool save)
{
appsettings()->gpg_command = global->gpg_command = executableOrEmpty(path);
internalSaveCommandPath(path, save, "GpgCommand");
if (!global->gpg_command.isEmpty()) {
GitPtr g = git();
g->configGpgProgram(global->gpg_command, true);
}
}
void MainWindow::setSshCommand(QString path, bool save)
{
appsettings()->ssh_command = executableOrEmpty(path);
internalSaveCommandPath(path, save, "SshCommand");
}
void MainWindow::logGitVersion()
{
GitPtr g = git();
QString s = g->version();
if (!s.isEmpty()) {
s += '\n';
writeLog(s);
}
}
void MainWindow::setUnknownRepositoryInfo()
{
setRepositoryInfo("---", "");
- Git g(m1->gcx, {}, {});
+ Git g(m1->gcx, {}, {}, {});
Git::User user = g.getUser(Git::Source::Global);
setWindowTitle_(user);
}
void MainWindow::internalClearRepositoryInfo()
{
setHeadId(QString());
setCurrentBranch(Git::Branch());
setServerType(ServerType::Standard);
m1->github = GitHubRepositoryInfo();
}
void MainWindow::checkUser()
{
- Git g(m1->gcx, {}, {});
+ Git g(m1->gcx, {}, {}, {});
while (1) {
Git::User user = g.getUser(Git::Source::Global);
if (!user.name.isEmpty() && !user.email.isEmpty()) {
return; // ok
}
if (!execSetGlobalUserDialog()) {
return;
}
}
}
void MainWindow::openRepository(bool validate, bool waitcursor, bool keep_selection)
{
if (validate) {
QString dir = currentWorkingCopyDir();
if (!QFileInfo(dir).isDir()) {
int r = QMessageBox::warning(this, tr("Open Repository"), dir + "\n\n" + tr("No such folder") + "\n\n" + tr("Remove from bookmark ?"), QMessageBox::Ok, QMessageBox::Cancel);
if (r == QMessageBox::Ok) {
removeSelectedRepositoryFromBookmark(false);
}
return;
}
if (!Git::isValidWorkingCopy(dir)) {
QMessageBox::warning(this, tr("Open Repository"), tr("Not a valid git repository") + "\n\n" + dir);
return;
}
}
if (waitcursor) {
OverrideWaitCursor;
openRepository(false, false, keep_selection);
return;
}
GitPtr g = git(); // ポインタの有効性チェックはしない(nullptrでも続行)
openRepository_(g, keep_selection);
}
void MainWindow::updateRepository()
{
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
OverrideWaitCursor;
openRepository_(g);
}
void MainWindow::reopenRepository(bool log, std::function<void(GitPtr const &)> const &callback)
{
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
OverrideWaitCursor;
if (log) {
setLogEnabled(g, true);
AsyncExecGitThread_ th(g, callback);
th.start();
while (1) {
if (th.wait(1)) break;
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
}
setLogEnabled(g, false);
} else {
callback(g);
}
openRepository_(g);
}
void MainWindow::openSelectedRepository()
{
RepositoryItem const *repo = selectedRepositoryItem();
if (repo) {
setCurrentRepository(*repo, true);
openRepository(true);
}
}
bool MainWindow::isThereUncommitedChanges() const
{
return m1->uncommited_changes;
}
/**
* @brief コミットに対応する差分情報を作成
* @param id コミットID
* @param ok
* @return
*/
-QList<Git::Diff> MainWindow::makeDiffs(QString id, bool *ok)
+QList<Git::Diff> MainWindow::makeDiffs(RepositoryWrapperFrame *frame, QString id, bool *ok)
{
QList<Git::Diff> out;
GitPtr g = git();
if (!isValidWorkingCopy(g)) {
if (ok) *ok = false;
return {};
}
Git::FileStatusList list = g->status_s();
setUncommitedChanges(!list.empty());
if (id.isEmpty() && !isThereUncommitedChanges()) {
- id = getObjCache()->revParse("HEAD");
+ id = getObjCache(frame)->revParse("HEAD");
}
QList<Git::SubmoduleItem> mods;
updateSubmodules(g, id, &mods);
setSubmodules(mods);
bool uncommited = (id.isEmpty() && isThereUncommitedChanges());
- GitDiff dm(getObjCache());
+ GitDiff dm(getObjCache(frame));
if (uncommited) {
dm.diff_uncommited(submodules(), &out);
} else {
dm.diff(id, submodules(), &out);
}
if (ok) *ok = true;
return out;
}
/**
* @brief コミットログを取得する
* @param g
* @return
*/
Git::CommitItemList MainWindow::retrieveCommitLog(GitPtr const &g)
{
Git::CommitItemList list = g->log(limitLogCount());
// 親子関係を調べて、順番が狂っていたら、修正する。
std::set<QString> set;
const size_t count = list.size();
size_t limit = count;
size_t i = 0;
while (i < count) {
size_t newpos = -1;
for (QString const &parent : list[i].parent_ids) {
if (set.find(parent) != set.end()) {
for (size_t j = 0; j < i; j++) {
if (parent == list[j].commit_id) {
if (newpos == (size_t)-1 || j < newpos) {
newpos = j;
}
qDebug() << "fix commit order" << list[i].commit_id;
break;
}
}
}
}
set.insert(set.end(), list[i].commit_id);
if (newpos != (size_t)-1) {
if (limit == 0) break; // まず無いと思うが、もし、無限ループに陥ったら
Git::CommitItem t = list[i];
t.strange_date = true;
list.erase(list.begin() + i);
list.insert(list.begin() + newpos, t);
i = newpos;
limit--;
}
i++;
}
return list;
}
-void MainWindow::queryBranches(GitPtr const &g)
+void MainWindow::queryBranches(RepositoryWrapperFrame *frame, GitPtr const &g)
{
Q_ASSERT(g);
- m1->branch_map.clear();
+// m1->branch_map.clear();
+ frame->branch_map.clear();
QList<Git::Branch> branches = g->branches();
for (Git::Branch const &b : branches) {
if (b.isCurrent()) {
setCurrentBranch(b);
}
- branchMapRef()[b.id].append(b);
+ branchMapRef(frame)[b.id].append(b);
}
}
-std::map<QString, QList<Git::Branch>> &MainWindow::branchMapRef()
+std::map<QString, QList<Git::Branch>> &MainWindow::branchMapRef(RepositoryWrapperFrame *frame)
{
- return m1->branch_map;
+// return m1->branch_map;
+ return frame->branch_map;
}
void MainWindow::updateRemoteInfo()
{
queryRemotes(git());
m1->current_remote_name = QString();
{
Git::Branch const &r = currentBranch();
m1->current_remote_name = r.remote;
}
if (m1->current_remote_name.isEmpty()) {
if (m1->remotes.size() == 1) {
m1->current_remote_name = m1->remotes[0];
}
}
emit remoteInfoChanged();
}
void MainWindow::updateWindowTitle(GitPtr const &g)
{
if (isValidWorkingCopy(g)) {
Git::User user = g->getUser(Git::Source::Default);
setWindowTitle_(user);
} else {
setUnknownRepositoryInfo();
}
}
QString MainWindow::makeCommitInfoText(RepositoryWrapperFrame *frame, int row, QList<BranchLabel> *label_list)
{
QString message_ex;
Git::CommitItem const *commit = &getLogs(frame)[row];
{ // branch
if (label_list) {
if (commit->commit_id == getHeadId()) {
BranchLabel label(BranchLabel::Head);
label.text = "HEAD";
label_list->push_back(label);
}
}
- QList<Git::Branch> list = findBranch(commit->commit_id);
+ QList<Git::Branch> list = findBranch(frame, commit->commit_id);
for (Git::Branch const &b : list) {
if (b.flags & Git::Branch::HeadDetachedAt) continue;
if (b.flags & Git::Branch::HeadDetachedFrom) continue;
BranchLabel label(BranchLabel::LocalBranch);
label.text = b.name;
if (!b.remote.isEmpty()) {
label.kind = BranchLabel::RemoteBranch;
label.text = "remotes" / b.remote / label.text;
}
if (b.ahead > 0) {
label.info += tr(", %1 ahead").arg(b.ahead);
}
if (b.behind > 0) {
label.info += tr(", %1 behind").arg(b.behind);
}
message_ex += " {" + label.text + label.info + '}';
if (label_list) label_list->push_back(label);
}
}
{ // tag
- QList<Git::Tag> list = findTag(commit->commit_id);
+ QList<Git::Tag> list = findTag(frame, commit->commit_id);
for (Git::Tag const &t : list) {
BranchLabel label(BranchLabel::Tag);
label.text = t.name;
message_ex += QString(" {#%1}").arg(label.text);
if (label_list) label_list->push_back(label);
}
}
return message_ex;
}
void MainWindow::removeRepositoryFromBookmark(int index, bool ask)
{
if (ask) {
int r = QMessageBox::warning(this, tr("Confirm Remove"), tr("Are you sure you want to remove the repository from bookmarks ?") + '\n' + tr("(Files will NOT be deleted)"), QMessageBox::Ok, QMessageBox::Cancel);
if (r != QMessageBox::Ok) return;
}
auto *repos = getReposPtr();
if (index >= 0 && index < repos->size()) {
repos->erase(repos->begin() + index);
saveRepositoryBookmarks();
updateRepositoriesList();
}
}
void MainWindow::clone(QString url, QString dir)
{
if (!isOnlineMode()) return;
if (dir.isEmpty()) {
dir = defaultWorkingDir();
}
while (1) {
QString ssh_key;
CloneDialog dlg(this, url, dir, &m1->gcx);
if (dlg.exec() != QDialog::Accepted) {
return;
}
const CloneDialog::Action action = dlg.action();
url = dlg.url();
dir = dlg.dir();
ssh_key = dlg.overridedSshKey();
RepositoryItem repos_item_data;
repos_item_data.local_dir = dir;
repos_item_data.local_dir.replace('\\', '/');
repos_item_data.name = makeRepositoryName(dir);
repos_item_data.ssh_key = ssh_key;
// クローン先ディレクトリを求める
Git::CloneData clone_data = Git::preclone(url, dir);
if (action == CloneDialog::Action::Clone) {
// 既存チェック
QFileInfo info(dir);
if (info.isFile()) {
QString msg = dir + "\n\n" + tr("A file with same name already exists");
QMessageBox::warning(this, tr("Clone"), msg);
continue;
}
if (info.isDir()) {
QString msg = dir + "\n\n" + tr("A folder with same name already exists");
QMessageBox::warning(this, tr("Clone"), msg);
continue;
}
// クローン先ディレクトリの存在チェック
QString basedir = misc::normalizePathSeparator(clone_data.basedir);
if (!QFileInfo(basedir).isDir()) {
int i = basedir.indexOf('/');
int j = basedir.indexOf('\\');
if (i < j) i = j;
if (i < 0) {
QString msg = basedir + "\n\n" + tr("Invalid folder");
QMessageBox::warning(this, tr("Clone"), msg);
continue;
}
QString msg = basedir + "\n\n" + tr("No such folder. Create it now ?");
if (QMessageBox::warning(this, tr("Clone"), msg, QMessageBox::Ok, QMessageBox::Cancel) != QMessageBox::Ok) {
continue;
}
// ディレクトリを作成
QString base = basedir.mid(0, i + 1);
QString sub = basedir.mid(i + 1);
QDir(base).mkpath(sub);
}
- GitPtr g = git(QString(), repos_item_data.ssh_key);
+ GitPtr g = git({}, {}, repos_item_data.ssh_key);
setPtyUserData(QVariant::fromValue<RepositoryItem>(repos_item_data));
setPtyCondition(PtyCondition::Clone);
setPtyProcessOk(true);
g->clone(clone_data, getPtyProcess());
} else if (action == CloneDialog::Action::AddExisting) {
addWorkingCopyDir(dir, true);
}
return; // done
}
}
void MainWindow::submodule_add(QString url, QString local_dir)
{
if (!isOnlineMode()) return;
if (local_dir.isEmpty()) return;
QString dir = local_dir;
while (1) {
SubmoduleAddDialog dlg(this, url, dir, &m1->gcx);
if (dlg.exec() != QDialog::Accepted) {
return;
}
url = dlg.url();
dir = dlg.dir();
const QString ssh_key = dlg.overridedSshKey();
RepositoryItem repos_item_data;
repos_item_data.local_dir = dir;
repos_item_data.local_dir.replace('\\', '/');
repos_item_data.name = makeRepositoryName(dir);
repos_item_data.ssh_key = ssh_key;
Git::CloneData data = Git::preclone(url, dir);
bool force = dlg.isForce();
- GitPtr g = git(local_dir, repos_item_data.ssh_key);
+ GitPtr g = git(local_dir, {}, repos_item_data.ssh_key);
auto callback = [&](GitPtr const &g){
g->submodule_add(data, force, getPtyProcess());
};
{
OverrideWaitCursor;
{
setLogEnabled(g, true);
AsyncExecGitThread_ th(g, callback);
th.start();
while (1) {
if (th.wait(1)) break;
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
}
setLogEnabled(g, false);
}
openRepository_(g);
}
return; // done
}
}
void MainWindow::commit(RepositoryWrapperFrame *frame, bool amend)
{
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
QString message;
QString previousMessage;
if (amend) {
message = getLogs(frame)[0].message;
} else {
QString id = g->getCherryPicking();
if (Git::isValidID(id)) {
message = g->getMessage(id);
} else {
for (Git::CommitItem const &item : getLogs(frame)) {
if (!item.commit_id.isEmpty()) {
previousMessage = item.message;
break;
}
}
}
}
while (1) {
Git::User user = g->getUser(Git::Source::Default);
QString sign_id = g->signingKey(Git::Source::Default);
gpg::Data key;
{
QList<gpg::Data> keys;
gpg::listKeys(global->gpg_command, &keys);
for (gpg::Data const &k : keys) {
if (k.id == sign_id) {
key = k;
}
}
}
CommitDialog dlg(this, currentRepositoryName(), user, key, previousMessage);
dlg.setText(message);
if (dlg.exec() == QDialog::Accepted) {
QString text = dlg.text();
if (text.isEmpty()) {
QMessageBox::warning(this, tr("Commit"), tr("Commit message can not be omitted."));
continue;
}
bool sign = dlg.isSigningEnabled();
bool ok;
if (amend || dlg.isAmend()) {
ok = g->commit_amend_m(text, sign, getPtyProcess());
} else {
ok = g->commit(text, sign, getPtyProcess());
}
if (ok) {
setForceFetch(true);
updateStatusBarText(frame);
openRepository(true);
} else {
QString err = g->errorMessage().trimmed();
err += "\n*** ";
err += tr("Failed to commit");
err += " ***\n";
writeLog(err);
}
}
break;
}
}
void MainWindow::commitAmend(RepositoryWrapperFrame *frame)
{
commit(frame, true);
}
void MainWindow::pushSetUpstream(QString const &remote, QString const &branch)
{
if (remote.isEmpty()) return;
if (branch.isEmpty()) return;
int exitcode = 0;
QString errormsg;
reopenRepository(true, [&](GitPtr g){
g->push_u(remote, branch, getPtyProcess());
while (1) {
if (getPtyProcess()->wait(1)) break;
QApplication::processEvents();
}
exitcode = getPtyProcess()->getExitCode();
errormsg = getPtyProcess()->getMessage();
});
if (exitcode == 128) {
if (errormsg.indexOf("Connection refused") >= 0) {
QMessageBox::critical(this, qApp->applicationName(), tr("Connection refused."));
return;
}
}
updateRemoteInfo();
}
bool MainWindow::pushSetUpstream(bool testonly)
{
GitPtr g = git();
if (!isValidWorkingCopy(g)) return false;
QStringList remotes = g->getRemotes();
QString current_branch = currentBranchName();
QStringList branches;
for (Git::Branch const &b : g->branches()) {
branches.push_back(b.name);
}
if (remotes.isEmpty() || branches.isEmpty()) {
return false;
}
if (testonly) {
return true;
}
PushDialog dlg(this, remotes, branches, PushDialog::RemoteBranch(QString(), current_branch));
if (dlg.exec() == QDialog::Accepted) {
PushDialog::Action a = dlg.action();
if (a == PushDialog::PushSimple) {
push();
} else if (a == PushDialog::PushSetUpstream) {
QString remote = dlg.remote();
QString branch = dlg.branch();
pushSetUpstream(remote, branch);
}
return true;
}
return false;
}
void MainWindow::push()
{
if (!isOnlineMode()) return;
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
if (g->getRemotes().isEmpty()) {
QMessageBox::warning(this, qApp->applicationName(), tr("No remote repository is registered."));
RepositoryItem const &repo = currentRepository();
execRepositoryPropertyDialog(repo, true);
return;
}
int exitcode = 0;
QString errormsg;
reopenRepository(true, [&](GitPtr g){
g->push(false, getPtyProcess());
while (1) {
if (getPtyProcess()->wait(1)) break;
QApplication::processEvents();
}
exitcode = getPtyProcess()->getExitCode();
errormsg = getPtyProcess()->getMessage();
});
if (exitcode == 128) {
if (errormsg.indexOf("no upstream branch") >= 0) {
QString brname = currentBranchName();
QString msg = tr("The current branch %1 has no upstream branch.");
msg = msg.arg(brname);
msg += '\n';
msg += tr("You try push --set-upstream");
QMessageBox::warning(this, qApp->applicationName(), msg);
pushSetUpstream(false);
return;
}
if (errormsg.indexOf("Connection refused") >= 0) {
QMessageBox::critical(this, qApp->applicationName(), tr("Connection refused."));
return;
}
}
}
#ifdef Q_OS_MAC
namespace {
bool isValidDir(QString const &dir)
{
if (dir.indexOf('\"') >= 0 || dir.indexOf('\\') >= 0) return false;
return QFileInfo(dir).isDir();
}
}
#include <QProcess>
#endif
void MainWindow::openTerminal(RepositoryItem const *repo)
{
runOnRepositoryDir([](QString dir){
#ifdef Q_OS_MAC
if (!isValidDir(dir)) return;
QString cmd = "open -n -a /Applications/Utilities/Terminal.app --args \"%1\"";
cmd = cmd.arg(dir);
QProcess::execute(cmd);
#else
Terminal::open(dir);
#endif
}, repo);
}
void MainWindow::openExplorer(RepositoryItem const *repo)
{
runOnRepositoryDir([](QString dir){
#ifdef Q_OS_MAC
if (!isValidDir(dir)) return;
QString cmd = "open \"%1\"";
cmd = cmd.arg(dir);
QProcess::execute(cmd);
#else
QDesktopServices::openUrl(dir);
#endif
}, repo);
}
Git::CommitItem const *MainWindow::selectedCommitItem(RepositoryWrapperFrame *frame) const
{
int i = selectedLogIndex(frame);
return commitItem(frame, i);
}
-void MainWindow::deleteBranch(Git::CommitItem const *commit)
+void MainWindow::deleteBranch(RepositoryWrapperFrame *frame, Git::CommitItem const *commit)
{
if (!commit) return;
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
QStringList all_branch_names;
QStringList current_local_branch_names;
{
- NamedCommitList named_commits = namedCommitItems(Branches);
+ NamedCommitList named_commits = namedCommitItems(frame, Branches);
for (NamedCommitItem const &item : named_commits) {
if (item.name == "HEAD") continue;
if (item.id == commit->commit_id) {
current_local_branch_names.push_back(item.name);
}
all_branch_names.push_back(item.name);
}
}
DeleteBranchDialog dlg(this, false, all_branch_names, current_local_branch_names);
if (dlg.exec() == QDialog::Accepted) {
setLogEnabled(g, true);
QStringList names = dlg.selectedBranchNames();
int count = 0;
for (QString const &name : names) {
if (g->git(QString("branch -D \"%1\"").arg(name))) {
count++;
} else {
writeLog(tr("Failed to delete the branch '%1'").arg(name) + '\n');
}
}
if (count > 0) {
openRepository(true, true, true);
}
}
}
void MainWindow::deleteBranch(RepositoryWrapperFrame *frame)
{
- deleteBranch(selectedCommitItem(frame));
+ deleteBranch(frame, selectedCommitItem(frame));
}
-QStringList MainWindow::remoteBranches(QString const &id, QStringList *all)
+QStringList MainWindow::remoteBranches(RepositoryWrapperFrame *frame, QString const &id, QStringList *all)
{
if (all) all->clear();
QStringList list;
GitPtr g = git();
if (isValidWorkingCopy(g)) {
- NamedCommitList named_commits = namedCommitItems(Branches | Remotes);
+ NamedCommitList named_commits = namedCommitItems(frame, Branches | Remotes);
for (NamedCommitItem const &item : named_commits) {
if (item.id == id && !item.remote.isEmpty()) {
list.push_back(item.remote / item.name);
}
if (all && !item.remote.isEmpty() && item.name != "HEAD") {
all->push_back(item.remote / item.name);
}
}
}
return list;
}
-void MainWindow::deleteRemoteBranch(Git::CommitItem const *commit)
+void MainWindow::deleteRemoteBranch(RepositoryWrapperFrame *frame, Git::CommitItem const *commit)
{
if (!commit) return;
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
QStringList all_branches;
- QStringList remote_branches = remoteBranches(commit->commit_id, &all_branches);
+ QStringList remote_branches = remoteBranches(frame, commit->commit_id, &all_branches);
if (remote_branches.isEmpty()) return;
DeleteBranchDialog dlg(this, true, all_branches, remote_branches);
if (dlg.exec() == QDialog::Accepted) {
setLogEnabled(g, true);
QStringList names = dlg.selectedBranchNames();
for (QString const &name : names) {
int i = name.indexOf('/');
if (i > 0) {
QString remote = name.mid(0, i);
QString branch = ':' + name.mid(i + 1);
pushSetUpstream(remote, branch);
}
}
}
}
bool MainWindow::askAreYouSureYouWantToRun(QString const &title, QString const &command)
{
QString message = tr("Are you sure you want to run the following command ?");
QString text = "%1\n\n%2";
text = text.arg(message).arg(command);
return QMessageBox::warning(this, title, text, QMessageBox::Ok, QMessageBox::Cancel) == QMessageBox::Ok;
}
bool MainWindow::editFile(QString const &path, QString const &title)
{
return TextEditDialog::editFile(this, path, title);
}
void MainWindow::resetFile(QStringList const &paths)
{
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
if (paths.isEmpty()) {
// nop
} else {
QString cmd = "git checkout -- \"%1\"";
cmd = cmd.arg(paths[0]);
if (askAreYouSureYouWantToRun(tr("Reset a file"), "> " + cmd)) {
for (QString const &path : paths) {
g->resetFile(path);
}
openRepository(true);
}
}
}
void MainWindow::saveRepositoryBookmark(RepositoryItem item)
{
if (item.local_dir.isEmpty()) return;
if (item.name.isEmpty()) {
item.name = tr("Unnamed");
}
auto *repos = getReposPtr();
bool done = false;
for (auto &repo : *repos) {
RepositoryItem *p = &repo;
if (item.local_dir == p->local_dir) {
*p = item;
done = true;
break;
}
}
if (!done) {
repos->push_back(item);
}
saveRepositoryBookmarks();
updateRepositoriesList();
}
void MainWindow::setCurrentRepository(RepositoryItem const &repo, bool clear_authentication)
{
if (clear_authentication) {
clearAuthentication();
}
m1->current_repo = repo;
}
void MainWindow::internalDeleteTags(QStringList const &tagnames)
{
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
if (!tagnames.isEmpty()) {
reopenRepository(false, [&](GitPtr g){
for (QString const &name : tagnames) {
g->delete_tag(name, true);
}
});
}
}
bool MainWindow::internalAddTag(RepositoryWrapperFrame *frame, QString const &name)
{
if (name.isEmpty()) return false;
GitPtr g = git();
if (!isValidWorkingCopy(g)) return false;
QString commit_id;
Git::CommitItem const *commit = selectedCommitItem(frame);
if (commit && !commit->commit_id.isEmpty()) {
commit_id = commit->commit_id;
}
if (!Git::isValidID(commit_id)) return false;
bool ok = false;
reopenRepository(false, [&](GitPtr g){
ok = g->tag(name, commit_id);
});
return ok;
}
-NamedCommitList MainWindow::namedCommitItems(int flags)
+NamedCommitList MainWindow::namedCommitItems(RepositoryWrapperFrame *frame, int flags)
{
NamedCommitList items;
if (flags & Branches) {
- for (auto const &pair: branchMapRef()) {
+ for (auto const &pair: branchMapRef(frame)) {
QList<Git::Branch> const &list = pair.second;
for (Git::Branch const &b : list) {
if (b.isHeadDetached()) continue;
if (flags & NamedCommitFlag::Remotes) {
// nop
} else {
if (!b.remote.isEmpty()) continue;
}
NamedCommitItem item;
if (b.remote.isEmpty()) {
if (b.name == "HEAD") {
item.type = NamedCommitItem::Type::None;
} else {
item.type = NamedCommitItem::Type::BranchLocal;
}
} else {
item.type = NamedCommitItem::Type::BranchRemote;
item.remote = b.remote;
}
item.name = b.name;
item.id = b.id;
items.push_back(item);
}
}
}
if (flags & Tags) {
- for (auto const &pair: *ptrTagMap()) {
+ for (auto const &pair: *ptrTagMap(frame)) {
QList<Git::Tag> const &list = pair.second;
for (Git::Tag const &t : list) {
NamedCommitItem item;
item.type = NamedCommitItem::Type::Tag;
item.name = t.name;
item.id = t.id;
if (item.name.startsWith("refs/tags/")) {
item.name = item.name.mid(10);
}
items.push_back(item);
}
}
}
return items;
}
int MainWindow::rowFromCommitId(RepositoryWrapperFrame *frame, QString const &id)
{
auto const &logs = getLogs(frame);
for (size_t i = 0; i < logs.size(); i++) {
Git::CommitItem const &item = logs[i];
if (item.commit_id == id) {
return (int)i;
}
}
return -1;
}
void MainWindow::createRepository(QString const &dir)
{
CreateRepositoryDialog dlg(this, dir);
if (dlg.exec() == QDialog::Accepted) {
QString path = dlg.path();
if (QFileInfo(path).isDir()) {
if (Git::isValidWorkingCopy(path)) {
// A valid git repository already exists there.
} else {
- GitPtr g = git(path);
+ GitPtr g = git(path, {}, {});
if (g->init()) {
QString name = dlg.name();
if (!name.isEmpty()) {
addWorkingCopyDir(path, name, true);
}
QString remote_name = dlg.remoteName();
QString remote_url = dlg.remoteURL();
if (!remote_name.isEmpty() && !remote_url.isEmpty()) {
Git::Remote r;
r.name = remote_name;
r.url = remote_url;
g->addRemoteURL(r);
}
}
}
} else {
// not dir
}
}
}
void MainWindow::setLogEnabled(GitPtr const &g, bool f)
{
if (f) {
g->setLogCallback(git_callback, this);
} else {
g->setLogCallback(nullptr, nullptr);
}
}
-QList<Git::Tag> MainWindow::findTag(QString const &id)
+QList<Git::Tag> MainWindow::findTag(RepositoryWrapperFrame *frame, QString const &id)
{
- auto it = ptrTagMap()->find(id);
- if (it != ptrTagMap()->end()) {
+ auto it = ptrTagMap(frame)->find(id);
+ if (it != ptrTagMap(frame)->end()) {
return it->second;
}
return QList<Git::Tag>();
}
void MainWindow::sshSetPassphrase(std::string const &user, std::string const &pass)
{
m1->ssh_passphrase_user = user;
m1->ssh_passphrase_pass = pass;
}
std::string MainWindow::sshPassphraseUser() const
{
return m1->ssh_passphrase_user;
}
std::string MainWindow::sshPassphrasePass() const
{
return m1->ssh_passphrase_pass;
}
void MainWindow::httpSetAuthentication(std::string const &user, std::string const &pass)
{
m1->http_uid = user;
m1->http_pwd = pass;
}
std::string MainWindow::httpAuthenticationUser() const
{
return m1->http_uid;
}
std::string MainWindow::httpAuthenticationPass() const
{
return m1->http_pwd;
}
void MainWindow::doGitCommand(std::function<void(GitPtr g)> const &callback)
{
GitPtr g = git();
if (isValidWorkingCopy(g)) {
OverrideWaitCursor;
callback(g);
openRepository(false, false);
}
}
diff --git a/src/BasicMainWindow.h b/src/BasicMainWindow.h
index 27c5273..3454a30 100644
--- a/src/BasicMainWindow.h
+++ b/src/BasicMainWindow.h
@@ -1,392 +1,393 @@
#ifndef BASICH
#define BASICH
//#include "AvatarLoader.h"
//#include "Git.h"
//#include "GitHubAPI.h"
//#include "GitObjectManager.h"
//#include "RepositoryData.h"
//#include "TextEditorTheme.h"
//#include "main.h"
//#include "webclient.h"
#include <QMainWindow>
//#include <functional>
//class ApplicationSettings;
//class AvatarLoader;
//class QListWidget;
//class QListWidgetItem;
//class QTreeWidgetItem;
//class WebContext;
struct GitHubRepositoryInfo {
QString owner_account_name;
QString repository_name;
};
#define PATH_PREFIX "*"
class BasicMainWindow : public QMainWindow {
Q_OBJECT
// friend class RepositoryPropertyDialog;
public:
enum {
IndexRole = Qt::UserRole,
FilePathRole,
DiffIndexRole,
- HunkIndexRole,
+ ObjectIdRole,
+// HunkIndexRole,
HeaderRole,
- IsSubmoduleRole,
+ SubmodulePathRole,
};
enum CloneOperation {
Clone,
SubmoduleAdd,
};
protected:
enum class PtyCondition {
None,
Clone,
Fetch,
Pull,
Push,
};
enum InteractionMode {
None,
Busy,
};
enum NamedCommitFlag {
Branches = 0x0001,
Tags = 0x0002,
Remotes = 0x0100,
};
enum class FilesListType {
SingleList,
SideBySide,
};
struct Task {
int index = 0;
int parent = 0;
Task() = default;
Task(int index, int parent)
: index(index)
, parent(parent)
{
}
};
struct Element {
int depth = 0;
std::vector<int> indexes;
};
enum {
GroupItem = -1,
};
private:
// static bool git_callback(void *cookie, const char *ptr, int len);
// QString selectCommand_(QString const &cmdname, QStringList const &cmdfiles, QStringList const &list, QString path, std::function<void (QString const &)> const &callback);
// QString selectCommand_(QString const &cmdname, QString const &cmdfile, QStringList const &list, QString const &path, std::function<void (QString const &)> const &callback);
// bool checkGitCommand();
// bool checkFileCommand();
// bool checkExecutable(QString const &path);
// void internalSaveCommandPath(QString const &path, bool save, QString const &name);
// QStringList whichCommand_(QString const &cmdfile1, QString const &cmdfile2 = QString());
// void setWindowTitle_(Git::User const &user);
// bool execSetGlobalUserDialog();
// bool saveByteArrayAs(const QByteArray &ba, QString const &dstpath);
// bool saveFileAs(QString const &srcpath, QString const &dstpath);
// bool saveBlobAs(QString const &id, QString const &dstpath);
// void updateFilesList(Git::CommitItem const &commit, bool wait);
// void revertAllFiles();
// void setCurrentRemoteName(QString const &name);
// void deleteTags(Git::CommitItem const &commit);
// bool isAvatarEnabled() const;
// bool isGitHub() const;
// QStringList remotes() const;
// QList<Git::Branch> findBranch(QString const &id);
// QString saveAsTemp(QString const &id);
// QString tempfileHeader() const;
// void deleteTempFiles();
// QString getCommitIdFromTag(QString const &tag);
// bool isValidRemoteURL(QString const &url, const QString &sshkey);
// QString getObjectID(QListWidgetItem *item);
// void addWorkingCopyDir(QString dir, QString name, bool open);
// static QString makeRepositoryName(QString const &loc);
// void clearAuthentication();
// RepositoryItem const *findRegisteredRepository(QString *workdir) const;
// void queryRemotes(const GitPtr &g);
// bool runOnRepositoryDir(std::function<void (QString)> const &callback, RepositoryItem const *repo);
// void clearSshAuthentication();
// QString executableOrEmpty(const QString &path);
// bool runOnRepositoryDir(const std::function<void (QString)> &callback, const RepositoryItem *repo);
protected:
// static QString getFilePath(QListWidgetItem *item);
// static bool isGroupItem(QTreeWidgetItem *item);
// static int indexOfLog(QListWidgetItem *item);
// static int indexOfDiff(QListWidgetItem *item);
// static int getHunkIndex(QListWidgetItem *item);
// void initNetworking();
// bool saveRepositoryBookmarks() const;
// QString getBookmarksFilePath() const;
// void stopPtyProcess();
// void abortPtyProcess();
// bool execWelcomeWizardDialog();
// void execRepositoryPropertyDialog(const RepositoryItem &repo, bool open_repository_menu = false);
// void execSetUserDialog(Git::User const &global_user, Git::User const &repo_user, QString const &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);
// void logGitVersion();
// void setUnknownRepositoryInfo();
// 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(GitPtr const &)> &callback);
// void openSelectedRepository();
// void checkRemoteUpdate();
// bool isThereUncommitedChanges() const;
// QList<Git::Diff> makeDiffs(QString id, bool *ok);
// void addDiffItems(const QList<Git::Diff> *diff_list, const std::function<void (ObjectData const &data)> &add_item);
// Git::CommitItemList retrieveCommitLog(const GitPtr &g);
// void queryBranches(const GitPtr &g);
// std::map<QString, QList<Git::Branch>> &branchMapRef();
// void updateCommitTableLater();
// void updateRemoteInfo();
// void updateWindowTitle(const GitPtr &g);
// QString makeCommitInfoText(int row, QList<Label> *label_list);
// void removeRepositoryFromBookmark(int index, bool ask);
// void clone(QString url = {}, QString dir = {});
// void submodule_add(QString url = {}, QString local_dir = {});
// void checkout();
// void commit(bool amend = false);
// void commitAmend();
// void pushSetUpstream(QString const &remote, QString const &branch);
// bool pushSetUpstream(bool testonly);
// void push();
// void openTerminal(RepositoryItem const *repo);
// void openExplorer(RepositoryItem const *repo);
// Git::CommitItem const *selectedCommitItem() const;
// void deleteBranch(Git::CommitItem const *commit);
// void deleteBranch();
// bool askAreYouSureYouWantToRun(QString const &title, QString const &command);
// bool editFile(QString const &path, QString const &title);
// void resetFile(QStringList const &paths);
// void saveRepositoryBookmark(RepositoryItem item);
// void set(RepositoryItem const &repo, bool clear_authentication);
// void internalDeleteTags(QStringList const &tagnames);
// bool internalAddTag(QString const &name);
// NamedCommitList namedCommitItems(int flags);
// int rowFromCommitId(QString const &id);
// void createRepository(QString const &dir);
// void setLogEnabled(const GitPtr &g, bool f);
// QList<Git::Tag> findTag(QString const &id);
// void sshSetPassphrase(std::string const &user, std::string const &pass);
// std::string sshPassphraseUser() const;
// std::string sshPassphrasePass() const;
// void httpSetAuthentication(std::string const &user, std::string const &pass);
// std::string httpAuthenticationUser() const;
// std::string httpAuthenticationPass() const;
public:
// Git::CommitItemList const &getLogs() const;
// Git::CommitItem const *getLog(int index) const;
protected:
// bool event(QEvent *event) override;
// void postUserFunctionEvent(const std::function<void(QVariant const &)> &fn, QVariant const &v = QVariant());
// void doGitCommand(std::function<void(GitPtr)> const &callback);
// Git::CommitItemList *getLogsPtr();
// void setLogs(const Git::CommitItemList &logs);
// void clearLogs();
// PtyProcess *getPtyProcess();
// PtyCondition getPtyCondition();
// bool getPtyProcessOk() const;
// void setPtyProcessOk(bool pty_process_ok);
// void setPtyUserData(QVariant const &userdata);
// void setPtyCondition(const PtyCondition &ptyCondition);
// QList<RepositoryItem> const &getRepos() const;
// QList<RepositoryItem> *getReposPtr();
// QString getCurrentRemoteName() const;
// AvatarLoader *getAvatarLoader();
// const AvatarLoader *getAvatarLoader() const;
// int *ptrUpdateFilesListCounter();
// int *ptrUpdateCommitTableCounter();
// bool interactionCanceled() const;
// void setInteractionCanceled(bool canceled);
// InteractionMode interactionMode() const;
// void setInteractionMode(const InteractionMode &im);
// QString getRepositoryFilterText() const;
// void setRepositoryFilterText(QString const &text);
// void setUncommitedChanges(bool uncommited_changes);
// QList<Git::Diff> *diffResult();
// std::map<QString, Git::Diff> *getDiffCacheMap();
// bool getRemoteChanged() const;
// void setRemoteChanged(bool remote_changed);
// void setServerType(const ServerType &server_type);
// GitHubRepositoryInfo *ptrGitHub();
// std::map<int, QList<Label>> *getLabelMap();
// const std::map<int, QList<Label>> *getLabelMap() const;
// void clearLabelMap();
// GitObjectCache *getObjCache();
// bool getForceFetch() const;
// void setForceFetch(bool force_fetch);
// std::map<QString, QList<Git::Tag>> *ptrTagMap();
// QString getHeadId() const;
// void setHeadId(QString const &head_id);
// void setPtyProcessCompletionData(QVariant const &value);
// QVariant const &getTempRepoForCloneCompleteV() const;
// void updateCommitGraph();
// bool fetch(const GitPtr &g, bool prune);
// bool fetch_tags_f(const GitPtr &g);
protected:
// virtual void setCurrentLogRow(int row) = 0;
// virtual void updateFilesList(QString id, bool wait) = 0;
// virtual void setRepositoryInfo(QString const &reponame, QString const &brname) = 0;
// virtual void deleteTags(QStringList const &tagnames) = 0;
// virtual void internalWriteLog(const char *ptr, int len) = 0;
// virtual void removeSelectedRepositoryFromBookmark(bool ask) = 0;
// virtual void openRepository_(GitPtr g, bool keep_selection = false) = 0;
// virtual void updateRepositoriesList() = 0;
// virtual void clearFileList() = 0;
// virtual RepositoryItem const *selectedRepositoryItem() const = 0;
// virtual void updateStatusBarText() {}
// void msgNoRepositorySelected();
// bool isRepositoryOpened() const;
// void updateSubmodules(GitPtr g, QString id);
// static std::pair<QString, QString> makeFileItemText(const ObjectData &data);
public:
explicit BasicMainWindow(QWidget *parent = nullptr);
~BasicMainWindow() override;
// ApplicationSettings *appsettings();
// const ApplicationSettings *appsettings() const;
// WebContext *webContext();
// QString gitCommand() const;
// void autoOpenRepository(QString dir);
// GitPtr git(QString const &dir, const QString &sshkey = {}) const;
// GitPtr git();
// GitPtr git(const Git::Submodule &submod);
// QPixmap getTransparentPixmap();
// QIcon committerIcon(int row) const;
// Git::CommitItem const *commitItem(int row) const;
// QIcon verifiedIcon(char s) const;
// virtual QString currentWorkingCopyDir() const;
// const QList<Label> *label(int row) const;
// bool saveAs(QString const &id, QString const &dstpath);
// bool testRemoteRepositoryValidity(QString const &url, const QString &sshkey);
// QString defaultWorkingDir() const;
// void addWorkingCopyDir(const QString &dir, bool open);
// bool queryCommit(QString const &id, Git::CommitItem *out);
// QAction *addMenuActionProperty(QMenu *menu);
// void checkout(QWidget *parent, Git::CommitItem const *commit, std::function<void()> accepted_callback = nullptr);
// void jumpToCommit(QString id);
// void execCommitViewWindow(Git::CommitItem const *commit);
// void execCommitExploreWindow(QWidget *parent, Git::CommitItem const *commit);
// void execCommitPropertyDialog(QWidget *parent, Git::CommitItem const *commit);
// void (QString const &path);
// void (QListWidgetItem *item);
// void execFilePropertyDialog(QListWidgetItem *item);
// QString selectGitCommand(bool save);
// QString selectFileCommand(bool save);
// QString selectGpgCommand(bool save);
// QString selectSshCommand(bool save);
// Git::Branch const &currentBranch() const;
// void setCurrentBranch(Git::Branch const &b);
// const RepositoryItem &() const;
// QString Name() const;
// QString currentRemoteName() const;
// QString currentBranchName() const;
// bool isValidWorkingCopy(const GitPtr &g) const;
// QString determinFileType_(QString const &path, bool mime, std::function<void (QString const &, QByteArray *)> const &callback) const;
// QString determinFileType(QString const &path, bool mime);
// QString determinFileType(QByteArray in, bool mime);
// QList<Git::Tag> queryTagList();
// int limitLogCount() const;
// TextEditorThemePtr themeForTextEditor();
// Git::Object cat_file_(const GitPtr &g, QString const &id);
// Git::Object cat_file(QString const &id);
// QString newTempFilePath();
// QString findFileID(QString const &commit_id, QString const &file);
// void updateFilesList2(QString const &id, QList<Git::Diff> *diff_list, QListWidget *listwidget);
// void setAppSettings(const ApplicationSettings &appsettings);
// QIcon getRepositoryIcon() const;
// QIcon getFolderIcon() const;
// QIcon getSignatureGoodIcon() const;
// QIcon getSignatureDubiousIcon() const;
// QIcon getSignatureBadIcon() const;
// QPixmap getTransparentPixmap() const;
// static QString abbrevCommitID(Git::CommitItem const &commit);
// QStringList findGitObject(const QString &id) const;
// void writeLog(const char *ptr, int len);
// void writeLog(QString const &str);
// void emitWriteLog(const QByteArray &ba);
// QList<Label> sortedLabels(int row) const;
// void changeSshKey(const QString &localdir, const QString &sshkey);
// void saveApplicationSettings();
// void loadApplicationSettings();
// void setDiffResult(const QList<Git::Diff> &diffs);
// const QList<Git::Submodule> &submodules() const;
// void setSubmodules(const QList<Git::Submodule> &submodules);
protected slots:
// void onAvatarUpdated();
public slots:
// void writeLog_(QByteArray ba);
//signals:
// void signalWriteLog(QByteArray ba);
// void remoteInfoChanged();
// void signalCheckRemoteUpdate();
private slots:
// void onAvatarUpdated();
};
#endif // BASICH
diff --git a/src/CloneDialog.cpp b/src/CloneDialog.cpp
index 5c22c5d..54a175a 100644
--- a/src/CloneDialog.cpp
+++ b/src/CloneDialog.cpp
@@ -1,156 +1,156 @@
#include "CloneDialog.h"
#include "ui_CloneDialog.h"
#include "ApplicationGlobal.h"
#include "MainWindow.h"
#include "SearchFromGitHubDialog.h"
#include "common/joinpath.h"
#include "common/misc.h"
#include <QFileDialog>
#include <QMessageBox>
#include <QStandardPaths>
#include <QThread>
enum SearchRepository {
None,
GitHub,
};
struct CloneDialog::Private {
QString url;
QString repo_name;
QString default_working_dir;
bool ok = false;
QString errmsg;
CloneDialog::Action action = CloneDialog::Action::Clone;
};
CloneDialog::CloneDialog(MainWindow *parent, QString const &url, QString const &defworkdir, Git::Context const *gcx)
: QDialog(parent)
, ui(new Ui::CloneDialog)
, m(new Private)
{
ui->setupUi(this);
Qt::WindowFlags flags = windowFlags();
flags &= ~Qt::WindowContextHelpButtonHint;
setWindowFlags(flags);
m->default_working_dir = defworkdir;
ui->lineEdit_working_dir->setText(m->default_working_dir);
ui->lineEdit_repo_location->setText(url);
ui->comboBox->addItem(tr("Search"));
ui->comboBox->addItem(tr("GitHub"));
ui->advanced_option->setSshKeyOverrigingEnabled(!gcx->ssh_command.isEmpty());
#ifdef Q_OS_MACX
ui->comboBox->setMinimumWidth(100);
#endif
ui->lineEdit_repo_location->setFocus();
}
CloneDialog::~CloneDialog()
{
delete m;
delete ui;
}
CloneDialog::Action CloneDialog::action() const
{
return m->action;
}
MainWindow *CloneDialog::mainwindow()
{
return qobject_cast<MainWindow *>(parent());
}
QString CloneDialog::url()
{
return ui->lineEdit_repo_location->text();
}
QString CloneDialog::dir()
{
return ui->lineEdit_working_dir->text();
}
void CloneDialog::on_lineEdit_repo_location_textChanged(QString const &text)
{
QString path;
int i = text.lastIndexOf('/');
int j = text.lastIndexOf('\\');
if (i < j) i = j;
j = text.size();
if (text.endsWith(".git")) {
j -= 4;
}
if (i >= 0 && i < j) {
path = text.mid(i, j - i);
}
m->repo_name = path;
path = m->default_working_dir / m->repo_name;
path = misc::normalizePathSeparator(path);
ui->lineEdit_working_dir->setText(path);
}
void CloneDialog::on_comboBox_currentIndexChanged(int index)
{
if (index == GitHub) {
SearchFromGitHubDialog dlg(this, mainwindow());
if (dlg.exec() == QDialog::Accepted) {
ui->lineEdit_repo_location->setText(dlg.url());
}
}
ui->comboBox->setCurrentIndex(0);
}
void CloneDialog::on_pushButton_test_clicked()
{
mainwindow()->testRemoteRepositoryValidity(url(), overridedSshKey());
}
void CloneDialog::on_pushButton_browse_clicked()
{
QString path = ui->lineEdit_working_dir->text();
path = QFileDialog::getExistingDirectory(this, tr("Checkout into"), path);
if (!path.isEmpty()) {
m->default_working_dir = path;
path = m->default_working_dir / m->repo_name;
path = misc::normalizePathSeparator(path);
ui->lineEdit_working_dir->setText(path);
}
}
void CloneDialog::on_pushButton_open_existing_clicked()
{
QString dir = mainwindow()->defaultWorkingDir();
dir = QFileDialog::getExistingDirectory(this, tr("Open existing directory"), dir);
if (QFileInfo(dir).isDir()) {
QString url;
- GitPtr g = mainwindow()->git(dir);
+ GitPtr g = mainwindow()->git(dir, {}, {});
QList<Git::Remote> vec;
if (g->isValidWorkingCopy()) {
g->getRemoteURLs(&vec);
}
for (Git::Remote const &r : vec) {
if (r.purpose == "push" || url.isEmpty()) {
url = r.url;
}
}
ui->lineEdit_repo_location->setText(url);
ui->lineEdit_working_dir->setText(dir);
m->action = CloneDialog::Action::AddExisting;
done(Accepted);
}
}
QString CloneDialog::overridedSshKey() const
{
return ui->advanced_option->sshKey();
}
diff --git a/src/CommitPropertyDialog.cpp b/src/CommitPropertyDialog.cpp
index 30e522c..35a64e1 100644
--- a/src/CommitPropertyDialog.cpp
+++ b/src/CommitPropertyDialog.cpp
@@ -1,171 +1,171 @@
#include "CommitPropertyDialog.h"
#include "ui_CommitPropertyDialog.h"
#include "ApplicationGlobal.h"
#include "AvatarLoader.h"
#include "MainWindow.h"
#include "common/misc.h"
#include "gpg.h"
#include "main.h"
struct CommitPropertyDialog::Private {
MainWindow *mainwindow;
Git::CommitItem commit;
AvatarLoader avatar_loader;
};
void CommitPropertyDialog::init(MainWindow *mw, RepositoryWrapperFrame *frame)
{
Qt::WindowFlags flags = windowFlags();
flags &= ~Qt::WindowContextHelpButtonHint;
setWindowFlags(flags);
ui->pushButton_jump->setVisible(false);
m->mainwindow = mw;
ui->lineEdit_message->setText(m->commit.message);
ui->lineEdit_commit_id->setText(m->commit.commit_id);
ui->lineEdit_date->setText(misc::makeDateTimeString(m->commit.commit_date));
ui->lineEdit_author->setText(m->commit.author);
ui->lineEdit_mail->setText(m->commit.email);
QString text;
for (QString const &id : m->commit.parent_ids) {
text += id + '\n';
}
ui->plainTextEdit_parent_ids->setPlainText(text);
gpg::Data key;
int n1 = m->commit.fingerprint.size();
if (n1 > 0) {
QList<gpg::Data> keys;
if (gpg::listKeys(global->gpg_command, &keys)) {
for (gpg::Data const &k : keys) {
int n2 = k.fingerprint.size();
if (n2 > 0) {
int n = std::min(n1, n2);
char const *p1 = m->commit.fingerprint.data() + n1 - n;
char const *p2 = k.fingerprint.data() + n2 - n;
if (memcmp(p1, p2, n) == 0) {
key = k;
break;
}
}
}
} else {
qDebug() << "Failed to get gpg keys";
}
if (key.id.isEmpty()) {
// gpgコマンドが登録されていないなど、keyidが取得できなかったとき
key.id = tr("<Unknown>");
}
}
if (key.id.isEmpty()) {
ui->frame_sign->setVisible(false);
} else {
{
int w = ui->label_signature_icon->width();
int h = ui->label_signature_icon->width();
QIcon icon = mainwindow()->verifiedIcon(m->commit.signature);
ui->label_signature_icon->setPixmap(icon.pixmap(w, h));
}
ui->lineEdit_sign_id->setText(key.id);
ui->lineEdit_sign_name->setText(key.name);
ui->lineEdit_sign_mail->setText(key.mail);
}
m->avatar_loader.start(mainwindow());
connect(&m->avatar_loader, &AvatarLoader::updated, [&](RepositoryWrapperFrameP frame){
updateAvatar(frame.pointer, false);
});
updateAvatar(frame, true);
}
void CommitPropertyDialog::updateAvatar(RepositoryWrapperFrame *frame, bool request)
{
if (!mainwindow()->isOnlineMode()) return;
auto SetAvatar = [&](RepositoryWrapperFrame *frame, QString const &email, QLabel *label){
if (mainwindow()->appsettings()->get_committer_icon) {
label->setFixedSize(QSize(48, 48));
QIcon icon = m->avatar_loader.fetch(frame, email.toStdString(), request);
setAvatar(icon, label);
} else {
label->setVisible(false);
}
};
SetAvatar(frame, ui->lineEdit_mail->text(), ui->label_user_avatar);
SetAvatar(frame, ui->lineEdit_sign_mail->text(), ui->label_sign_avatar);
}
CommitPropertyDialog::CommitPropertyDialog(QWidget *parent, MainWindow *mw, RepositoryWrapperFrame *frame, Git::CommitItem const *commit)
: QDialog(parent)
, ui(new Ui::CommitPropertyDialog)
, m(new Private)
{
ui->setupUi(this);
m->commit = *commit;
init(mw, frame);
}
CommitPropertyDialog::CommitPropertyDialog(QWidget *parent, MainWindow *mw, RepositoryWrapperFrame *frame, QString const &commit_id)
: QDialog(parent)
, ui(new Ui::CommitPropertyDialog)
, m(new Private)
{
ui->setupUi(this);
mw->queryCommit(commit_id, &m->commit);
init(mw, frame);
}
CommitPropertyDialog::~CommitPropertyDialog()
{
m->avatar_loader.stop();
delete m;
delete ui;
}
MainWindow *CommitPropertyDialog::mainwindow()
{
return m->mainwindow;
}
void CommitPropertyDialog::setAvatar(QIcon const &icon, QLabel *label)
{
QPixmap pm = icon.pixmap(label->size());
label->setPixmap(pm);
}
void CommitPropertyDialog::showCheckoutButton(bool f)
{
ui->pushButton_checkout->setVisible(f);
}
void CommitPropertyDialog::showJumpButton(bool f)
{
ui->pushButton_jump->setVisible(f);
}
void CommitPropertyDialog::on_pushButton_checkout_clicked()
{
- mainwindow()->checkout(this, &m->commit, [&](){ hide(); });
+ mainwindow()->checkout(mainwindow()->frame(), this, &m->commit, [&](){ hide(); });
done(QDialog::Rejected);
}
void CommitPropertyDialog::on_pushButton_jump_clicked()
{
mainwindow()->jumpToCommit(mainwindow()->frame(), m->commit.commit_id);
done(QDialog::Accepted);
}
void CommitPropertyDialog::on_pushButton_details_clicked()
{
mainwindow()->execCommitViewWindow(&m->commit);
}
void CommitPropertyDialog::on_pushButton_explorer_clicked()
{
- mainwindow()->execCommitExploreWindow(this, &m->commit);
+ mainwindow()->execCommitExploreWindow(mainwindow()->frame(), this, &m->commit);
}
diff --git a/src/CommitViewWindow.cpp b/src/CommitViewWindow.cpp
index ffcca25..5b5c0b4 100644
--- a/src/CommitViewWindow.cpp
+++ b/src/CommitViewWindow.cpp
@@ -1,71 +1,71 @@
#include "CommitViewWindow.h"
#include "ui_CommitViewWindow.h"
#include <QMenu>
struct CommitViewWindow::Private {
Git::CommitItem const *commit = nullptr;
QList<Git::Diff> diff_list;
};
CommitViewWindow::CommitViewWindow(MainWindow *parent, Git::CommitItem const *commit)
: QDialog(parent)
, ui(new Ui::CommitViewWindow)
, m(new Private)
{
ui->setupUi(this);
Qt::WindowFlags flags = windowFlags();
flags &= ~Qt::WindowContextHelpButtonHint;
setWindowFlags(flags);
m->commit = commit;
ui->widget_diff->bind(mainwindow());
ui->lineEdit_message->setText(m->commit->message);
ui->lineEdit_id->setText(m->commit->commit_id);
- mainwindow()->updateFilesList2(m->commit->commit_id, &m->diff_list, ui->listWidget_files);
+ mainwindow()->updateFilesList2(mainwindow()->frame(), m->commit->commit_id, &m->diff_list, ui->listWidget_files);
ui->listWidget_files->setCurrentRow(0);
}
CommitViewWindow::~CommitViewWindow()
{
delete m;
delete ui;
}
MainWindow *CommitViewWindow::mainwindow()
{
return qobject_cast<MainWindow *>(parent());
}
void CommitViewWindow::on_listWidget_files_currentRowChanged(int currentRow)
{
if (currentRow >= 0 && currentRow < m->diff_list.size()) {
Git::Diff const &diff = m->diff_list[currentRow];
ui->widget_diff->updateDiffView(diff, false);
}
}
void CommitViewWindow::on_listWidget_files_customContextMenuRequested(const QPoint &pos)
{
GitPtr g = mainwindow()->git();
if (!mainwindow()->isValidWorkingCopy(g)) return;
QMenu menu;
QAction *a_history = menu.addAction(tr("History"));
QAction *a_properties = mainwindow()->addMenuActionProperty(&menu);
QPoint pt = ui->listWidget_files->mapToGlobal(pos) + QPoint(8, -8);
QAction *a = menu.exec(pt);
if (a) {
QListWidgetItem *item = ui->listWidget_files->currentItem();
if (a == a_history) {
mainwindow()->execFileHistory(item);
} else if (a == a_properties) {
- mainwindow()->execFilePropertyDialog(item);
+ mainwindow()->showObjectProperty(item);
}
}
}
diff --git a/src/FileDiffWidget.cpp b/src/FileDiffWidget.cpp
index 16861f7..bb6a5d0 100644
--- a/src/FileDiffWidget.cpp
+++ b/src/FileDiffWidget.cpp
@@ -1,793 +1,793 @@
#include "ApplicationGlobal.h"
#include "BigDiffWindow.h"
#include "FileDiffWidget.h"
#include "GitDiff.h"
#include "MainWindow.h"
#include "Theme.h"
#include "common/joinpath.h"
#include "common/misc.h"
#include "ui_FileDiffWidget.h"
#include <QBuffer>
#include <QDebug>
#include <QKeyEvent>
#include <QMenu>
#include <QPainter>
#include <QStyle>
#include <QTextCodec>
#include <memory>
enum {
DiffIndexRole = Qt::UserRole,
};
struct FileDiffWidget::Private {
MainWindow *mainwindow = nullptr;
FileDiffWidget::InitParam_ init_param_;
Git::CommitItemList commit_item_list;
std::vector<std::string> original_lines;
TextEditorEnginePtr engine_left;
TextEditorEnginePtr engine_right;
TextDiffLineList left_lines;
TextDiffLineList right_lines;
int max_line_length = 0;
int term_cursor_row = 0;
int term_cursor_col = 0;
QTextCodec *text_codec = nullptr;
};
FileDiffWidget::FileDiffWidget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::FileDiffWidget)
, m(new Private)
{
ui->setupUi(this);
Qt::WindowFlags flags = windowFlags();
flags &= ~Qt::WindowContextHelpButtonHint;
setWindowFlags(flags);
ui->widget_diff_slider->init([&](DiffPane pane, int width, int height){
return makeDiffPixmap(pane, width, height);
}, global->theme);
connect(ui->widget_diff_slider, &FileDiffSliderWidget::valueChanged, this, &FileDiffWidget::scrollTo);
connect(ui->widget_diff_left->texteditor(), &TextEditorWidget::moved, this, &FileDiffWidget::onMoved);
connect(ui->widget_diff_right->texteditor(), &TextEditorWidget::moved, this, &FileDiffWidget::onMoved);
setFocusAcceptable(Qt::ClickFocus);
QWidget::setTabOrder(ui->widget_diff_slider, ui->widget_diff_left);
QWidget::setTabOrder(ui->widget_diff_left, ui->widget_diff_right);
int n = style()->pixelMetric(QStyle::PM_ScrollBarExtent);
ui->toolButton_fullscreen->setFixedSize(n, n);
setMaximizeButtonEnabled(true);
m->engine_left = std::make_shared<TextEditorEngine>();
m->engine_right = std::make_shared<TextEditorEngine>();
ui->widget_diff_left->setDiffMode(m->engine_left, ui->verticalScrollBar, ui->horizontalScrollBar);
ui->widget_diff_right->setDiffMode(m->engine_right, ui->verticalScrollBar, ui->horizontalScrollBar);
setViewType(FileViewType::None);
}
FileDiffWidget::~FileDiffWidget()
{
delete m;
delete ui;
}
void FileDiffWidget::setViewType(FileViewType type)
{
ui->widget_diff_left->setViewType(type);
ui->widget_diff_right->setViewType(type);
}
void FileDiffWidget::setMaximizeButtonEnabled(bool f)
{
ui->toolButton_fullscreen->setVisible(f);
ui->toolButton_fullscreen->setEnabled(f);
}
FileDiffWidget::ViewStyle FileDiffWidget::viewstyle() const
{
return m->init_param_.view_style;
}
void FileDiffWidget::bind(MainWindow *mw)
{
Q_ASSERT(mw);
m->mainwindow = mw;
ui->widget_diff_left->bind(mw, this, ui->verticalScrollBar, ui->horizontalScrollBar, mw->themeForTextEditor());
ui->widget_diff_right->bind(mw, this, ui->verticalScrollBar, ui->horizontalScrollBar, mw->themeForTextEditor());
connect(ui->verticalScrollBar, &QAbstractSlider::valueChanged, this, &FileDiffWidget::onVerticalScrollValueChanged);
connect(ui->horizontalScrollBar, &QAbstractSlider::valueChanged, this, &FileDiffWidget::onHorizontalScrollValueChanged);
}
MainWindow *FileDiffWidget::mainwindow()
{
return m->mainwindow;
}
GitPtr FileDiffWidget::git()
{
if (!mainwindow()) {
qDebug() << "Maybe, you forgot to call FileDiffWidget::bind() ?";
return GitPtr();
}
return mainwindow()->git();
}
Git::Object FileDiffWidget::cat_file(GitPtr const &/*g*/, QString const &id)
{
- return mainwindow()->cat_file(id);
+ return mainwindow()->cat_file(mainwindow()->frame(), id);
}
int FileDiffWidget::totalTextLines() const
{
return m->engine_left->document.lines.size();
}
void FileDiffWidget::clearDiffView()
{
ui->widget_diff_slider->clear(false);
}
int FileDiffWidget::fileviewHeight() const
{
return ui->widget_diff_left->height();
}
void FileDiffWidget::resetScrollBarValue()
{
ui->verticalScrollBar->setValue(0);
ui->horizontalScrollBar->setValue(0);
}
void FileDiffWidget::scrollToBottom()
{
QScrollBar *sb = ui->verticalScrollBar;
sb->setValue(sb->maximum());
}
void FileDiffWidget::updateSliderCursor()
{
if (viewstyle() == SideBySideText) {
ui->widget_diff_slider->clear(true);
}
}
void FileDiffWidget::updateControls()
{
updateSliderCursor();
}
void FileDiffWidget::makeSideBySideDiffData(Git::Diff const &diff, std::vector<std::string> const &original_lines, TextDiffLineList *left_lines, TextDiffLineList *right_lines)
{
left_lines->clear();
right_lines->clear();
m->original_lines = original_lines;
size_t linenum = original_lines.size();
std::vector<HunkItem> hunks;
int number = 0;
for (auto it = diff.hunks.begin(); it != diff.hunks.end(); it++, number++) {
std::string at = it->at;
if (strncmp(at.c_str(), "@@ -", 4) == 0) {
size_t pos = 0;
size_t len = 0;
char const *p = at.c_str() + 4;
auto ParseNumber = [&](){
size_t v = 0;
while (isdigit(*p & 0xff)) {
v = v * 10 + (*p - '0');
p++;
}
return v;
};
pos = ParseNumber();
if (*p == ',') {
p++;
len = ParseNumber();
} else {
len = 1;
}
if (pos > 0) pos--;
HunkItem item;
item.hunk_number = number;
item.pos = pos;
item.len = len;
for (std::string const &line : it->lines) {
item.lines.push_back(line);
}
hunks.push_back(item);
}
}
std::sort(hunks.begin(), hunks.end(), [](HunkItem const &l, HunkItem const &r){
return l.pos + l.len < r.pos + r.len;
});
size_t h = hunks.size();
while (linenum > 0 || h > 0) {
while (h > 0) {
int hunk_number = h - 1;
HunkItem const &hi = hunks[hunk_number];
if (hi.pos + hi.len < linenum) {
break;
}
std::vector<TextDiffLine> tmp_left;
std::vector<TextDiffLine> tmp_right;
int minus = 0;
int plus = 0;
auto FlushBlank = [&](){
while (minus < plus) {
tmp_left.emplace_back();
minus++;
}
while (minus > plus) {
tmp_right.emplace_back();
plus++;
}
minus = plus = 0;
};
for (auto line : hi.lines) {
int c = line[0] & 0xff;
line = line.substr(1);
if (c == '-') {
minus++;
TextDiffLine l(line, TextDiffLine::Del);
l.hunk_number = hunk_number;
tmp_left.push_back(l);
} else if (c == '+') {
plus++;
TextDiffLine l(line, TextDiffLine::Add);
l.hunk_number = hunk_number;
tmp_right.push_back(l);
} else {
FlushBlank();
TextDiffLine l(line, TextDiffLine::Normal);
l.hunk_number = hunk_number;
tmp_left.push_back(l);
tmp_right.push_back(l);
}
}
FlushBlank();
auto ComplementNewLine = [](std::vector<TextDiffLine> *lines){
for (TextDiffLine &line : *lines) {
int n = line.text.size();
if (n > 0) {
int c = line.text[n - 1] & 0xff;
if (c != '\r' && c != '\n') {
line.text.push_back('\n');
}
}
}
};
ComplementNewLine(&tmp_left);
ComplementNewLine(&tmp_right);
for (auto it = tmp_left.rbegin(); it != tmp_left.rend(); it++) {
TextDiffLine l(*it);
if (m->text_codec) {
if (!l.text.isEmpty()) {
QString s = QString::fromUtf8(l.text.data(), l.text.size());
l.text = m->text_codec->fromUnicode(s);
}
}
left_lines->push_back(l);
}
for (auto it = tmp_right.rbegin(); it != tmp_right.rend(); it++) {
TextDiffLine l(*it);
if (m->text_codec) {
if (!l.text.isEmpty()) {
QString s = QString::fromUtf8(l.text.data(), l.text.size());
l.text = m->text_codec->fromUnicode(s);
}
}
right_lines->push_back(l);
}
linenum = hi.pos;
h--;
}
if (linenum > 0) {
linenum--;
if (linenum < (size_t)original_lines.size()) {
std::string line = original_lines[linenum];
left_lines->push_back(TextDiffLine(line, TextDiffLine::Normal));
right_lines->push_back(TextDiffLine(line, TextDiffLine::Normal));
}
}
}
std::reverse(left_lines->begin(), left_lines->end());
std::reverse(right_lines->begin(), right_lines->end());
}
void FileDiffWidget::setDiffText(Git::Diff const &diff, TextDiffLineList const &left, TextDiffLineList const &right)
{
m->max_line_length = 0;
enum Pane {
Left,
Right,
};
auto SetLineNumber = [&](TextDiffLineList const &lines, Pane pane, TextDiffLineList *out){
out->clear();
int linenum = 1;
for (TextDiffLine const &line : lines) {
TextDiffLine item = line;
switch (item.type) {
case TextDiffLine::Normal:
item.line_number = linenum++;
break;
case TextDiffLine::Add:
if (pane == Pane::Right) {
item.line_number = linenum++;
}
break;
case TextDiffLine::Del:
if (pane == Pane::Left) {
item.line_number = linenum++;
}
break;
default:
item.line_number = linenum; // 行番号は設定するが、インクリメントはしない
break;
}
out->push_back(item);
}
};
SetLineNumber(left, Pane::Left, &m->left_lines);
SetLineNumber(right, Pane::Right, &m->right_lines);
ui->widget_diff_left->setText(&m->left_lines, mainwindow(), diff.blob.a_id, diff.path);
ui->widget_diff_right->setText(&m->right_lines, mainwindow(), diff.blob.b_id, diff.path);
refrectScrollBar();
ui->widget_diff_slider->clear(true);
}
FileViewType FileDiffWidget::setupPreviewWidget()
{
clearDiffView();
QString mimetype_l = mainwindow()->determinFileType(m->init_param_.bytes_a, true);
QString mimetype_r = mainwindow()->determinFileType(m->init_param_.bytes_b, true);
if (misc::isImage(mimetype_l) || misc::isImage(mimetype_r)) { // image
ui->verticalScrollBar->setVisible(false);
ui->horizontalScrollBar->setVisible(false);
ui->widget_diff_slider->setVisible(false);
ui->widget_diff_left->setImage(mimetype_l, m->init_param_.bytes_a, m->init_param_.diff.blob.a_id, m->init_param_.diff.path);
ui->widget_diff_right->setImage(mimetype_r, m->init_param_.bytes_b, m->init_param_.diff.blob.b_id, m->init_param_.diff.path);
return FileViewType::Image;
} else { // text
ui->verticalScrollBar->setVisible(true);
ui->horizontalScrollBar->setVisible(true);
ui->widget_diff_slider->setVisible(true);
setViewType(FileViewType::Text);
return FileViewType::Text;
}
}
void FileDiffWidget::setSingleFile(QByteArray const &ba, QString const &id, QString const &path)
{
m->init_param_ = InitParam_();
m->init_param_.view_style = FileDiffWidget::ViewStyle::SingleFile;
m->init_param_.bytes_a = ba;
m->init_param_.diff.path = path;
m->init_param_.diff.blob.a_id = id;
}
void FileDiffWidget::setOriginalLines_(QByteArray const &ba, Git::SubmoduleItem const *submodule, Git::CommitItem const *submodule_commit)
{
m->original_lines.clear();
if (!ba.isEmpty()) {
char const *begin = ba.data();
char const *end = begin + ba.size();
misc::splitLines(begin, end, &m->original_lines, true);
}
}
void FileDiffWidget::setLeftOnly(QByteArray const &ba, Git::Diff const &diff)
{
m->init_param_ = InitParam_();
m->init_param_.view_style = FileDiffWidget::ViewStyle::LeftOnly;
m->init_param_.bytes_a = ba;
m->init_param_.diff = diff;
setOriginalLines_(ba, &diff.a_submodule.item, &diff.a_submodule.commit);
if (setupPreviewWidget() == FileViewType::Text) {
TextDiffLineList left_lines;
TextDiffLineList right_lines;
for (std::string const &line : m->original_lines) {
left_lines.push_back(TextDiffLine(line, TextDiffLine::Del));
right_lines.push_back(TextDiffLine());
}
setDiffText(diff, left_lines, right_lines);
}
}
bool FileDiffWidget::setSubmodule(Git::Diff const &diff)
{
Git::SubmoduleItem const &submod_a = diff.a_submodule.item;
Git::SubmoduleItem const &submod_b = diff.b_submodule.item;
Git::CommitItem const &submod_commit_a = diff.a_submodule.commit;
Git::CommitItem const &submod_commit_b = diff.b_submodule.commit;
if (submod_a || submod_b) {
auto Text = [](Git::SubmoduleItem const *submodule, Git::CommitItem const *submodule_commit, TextDiffLineList *out){
*out = {};
if (submodule && *submodule) {
QString text;
text += "name: " + submodule->name + '\n';
text += "path: " + submodule->path + '\n';
text += "url: " + submodule->url + '\n';
text += "commit: " + submodule->id + '\n';
text += "date: " + misc::makeDateTimeString(submodule_commit->commit_date) + '\n';
text += "author: " + submodule_commit->author + '\n';
text += "email: " + submodule_commit->email + '\n';
text += '\n';
text += submodule_commit->message;
for (QString const &line : misc::splitLines(text)) {
out->push_back(Document::Line(line.toStdString()));
}
}
};
TextDiffLineList left_lines;
TextDiffLineList right_lines;
if (submod_a) {
Text(&submod_a, &submod_commit_a, &left_lines);
}
if (submod_b) {
Text(&submod_b, &submod_commit_b, &right_lines);
}
setDiffText(diff, left_lines, right_lines);
return true;
}
return false;
}
void FileDiffWidget::setRightOnly(QByteArray const &ba, Git::Diff const &diff)
{
m->init_param_ = InitParam_();
m->init_param_.view_style = FileDiffWidget::ViewStyle::RightOnly;
m->init_param_.bytes_b = ba;
m->init_param_.diff = diff;
setOriginalLines_(ba, &diff.b_submodule.item, &diff.b_submodule.commit);
if (setSubmodule(diff)) {
// ok
} else if (setupPreviewWidget() == FileViewType::Text) {
TextDiffLineList left_lines;
TextDiffLineList right_lines;
for (std::string const &line : m->original_lines) {
left_lines.push_back(TextDiffLine());
right_lines.push_back(TextDiffLine(line, TextDiffLine::Add));
}
setDiffText(diff, left_lines, right_lines);
}
}
void FileDiffWidget::setSideBySide(QByteArray const &ba, Git::Diff const &diff, bool uncommited, QString const &workingdir)
{
m->init_param_ = InitParam_();
m->init_param_.view_style = FileDiffWidget::ViewStyle::SideBySideText;
m->init_param_.bytes_a = ba;
m->init_param_.diff = diff;
m->init_param_.uncommited = uncommited;
m->init_param_.workingdir = workingdir;
setOriginalLines_(ba, {}, {});
if (setSubmodule(diff)) {
// ok
} else {
if (setupPreviewWidget() == FileViewType::Text) {
TextDiffLineList left_lines;
TextDiffLineList right_lines;
makeSideBySideDiffData(diff, m->original_lines, &left_lines, &right_lines);
setDiffText(diff, left_lines, right_lines);
}
}
}
void FileDiffWidget::setSideBySide_(QByteArray const &ba_a, QByteArray const &ba_b, QString const &workingdir)
{
m->init_param_ = InitParam_();
m->init_param_.view_style = FileDiffWidget::ViewStyle::SideBySideImage;
m->init_param_.bytes_a = ba_a;
m->init_param_.bytes_b = ba_b;
m->init_param_.workingdir = workingdir;
setOriginalLines_(ba_a, {}, {});
if (setupPreviewWidget() == FileViewType::Text) {
TextDiffLineList left_lines;
TextDiffLineList right_lines;
makeSideBySideDiffData(m->init_param_.diff, m->original_lines, &left_lines, &right_lines);
setDiffText(m->init_param_.diff, left_lines, right_lines);
}
}
QString FileDiffWidget::diffObjects(GitPtr const &g, QString const &a_id, QString const &b_id)
{
if (m->text_codec) {
- Git::Object obj_a = mainwindow()->cat_file_(g, a_id);
- Git::Object obj_b = mainwindow()->cat_file_(g, b_id);
+ Git::Object obj_a = mainwindow()->cat_file_(mainwindow()->frame(), g, a_id);
+ Git::Object obj_b = mainwindow()->cat_file_(mainwindow()->frame(), g, b_id);
if (obj_b.type == Git::Object::Type::UNKNOWN) {
obj_b.type = Git::Object::Type::BLOB;
}
if (obj_a.type == Git::Object::Type::BLOB && obj_b.type == Git::Object::Type::BLOB) {
QString path_a = mainwindow()->newTempFilePath();
QString path_b = mainwindow()->newTempFilePath();
QFile file_a(path_a);
QFile file_b(path_b);
if (file_a.open(QFile::WriteOnly) && file_b.open(QFile::WriteOnly)) {
file_a.write(m->text_codec->toUnicode(obj_a.content).toUtf8());
file_b.write(m->text_codec->toUnicode(obj_b.content).toUtf8());
file_a.close();
file_b.close();
QString s = g->diff_file(path_a, path_b);
file_a.remove();
file_b.remove();
return s;
}
}
}
return GitDiff::diffObjects(g, a_id, b_id);
}
bool FileDiffWidget::isValidID_(QString const &id)
{
if (id.startsWith(PATH_PREFIX)) {
return true;
}
return Git::isValidID(id);
}
void FileDiffWidget::updateDiffView(Git::Diff const &info, bool uncommited)
{
GitPtr g = git();
if (!g) return;
if (!g->isValidWorkingCopy()) return;
if (isValidID_(info.blob.a_id) && isValidID_(info.blob.b_id)) {
Git::Object obj_a = cat_file(g, info.blob.a_id);
Git::Object obj_b = cat_file(g, info.blob.b_id);
QString mime_a = mainwindow()->determinFileType(obj_a.content, true);
QString mime_b = mainwindow()->determinFileType(obj_b.content, true);
if (misc::isImage(mime_a) && misc::isImage(mime_b)) {
- setSideBySide_(obj_a.content, obj_b.content, g->workingRepositoryDir());
+ setSideBySide_(obj_a.content, obj_b.content, g->workingDir());
return;
}
}
{
Git::Diff diff;
if (isValidID_(info.blob.a_id) && isValidID_(info.blob.b_id)) {
std::string text = diffObjects(g, info.blob.a_id, info.blob.b_id).toStdString();
GitDiff::parseDiff(text, &info, &diff);
} else {
diff = info;
}
Git::Object obj;
if (isValidID_(diff.blob.a_id)) { // 左が有効
obj = cat_file(g, diff.blob.a_id);
if (isValidID_(diff.blob.b_id)) { // 右が有効
- setSideBySide(obj.content, diff, uncommited, g->workingRepositoryDir()); // 通常のdiff表示
+ setSideBySide(obj.content, diff, uncommited, g->workingDir()); // 通常のdiff表示
} else {
setLeftOnly(obj.content, diff); // 右が無効の時は、削除されたファイル
}
} else if (isValidID_(diff.blob.b_id)) { // 左が無効で右が有効の時は、追加されたファイル
obj = cat_file(g, diff.blob.b_id);
setRightOnly(obj.content, diff);
}
}
}
void FileDiffWidget::updateDiffView(QString const &id_left, QString const &id_right, QString const &path)
{
GitPtr g = git();
if (!g) return;
if (!g->isValidWorkingCopy()) return;
Git::Diff diff;
diff.path = path;
diff.blob.a_id = id_left;
diff.blob.b_id = id_right;
diff.mode = "0";
std::string text = diffObjects(g, diff.blob.a_id, diff.blob.b_id).toStdString();
GitDiff::parseDiff(text, &diff, &diff);
Git::Object obj = cat_file(g, diff.blob.a_id);
- setSideBySide(obj.content, diff, false, g->workingRepositoryDir());
+ setSideBySide(obj.content, diff, false, g->workingDir());
ui->widget_diff_slider->clear(false);
resetScrollBarValue();
updateControls();
ui->widget_diff_slider->update();
}
void FileDiffWidget::resizeEvent(QResizeEvent *)
{
refrectScrollBar();
}
void FileDiffWidget::keyPressEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_Escape) {
event->ignore(); // Escが押されたとき、親ウィジェットに処理させる。(通常、ウィンドウを閉じる、など)
return;
}
if (focusWidget() == ui->widget_diff_left->texteditor()) {
ui->widget_diff_left->write(event);
} else if (focusWidget() == ui->widget_diff_right->texteditor()) {
ui->widget_diff_right->write(event);
}
}
void FileDiffWidget::scrollTo(int value)
{
ui->verticalScrollBar->setValue(value);
}
void FileDiffWidget::onVerticalScrollValueChanged(int)
{
refrectScrollBar();
}
void FileDiffWidget::onHorizontalScrollValueChanged(int)
{
refrectScrollBar();
}
void FileDiffWidget::onDiffWidgetWheelScroll(int lines)
{
while (lines > 0) {
ui->verticalScrollBar->triggerAction(QScrollBar::SliderSingleStepAdd);
lines--;
}
while (lines < 0) {
ui->verticalScrollBar->triggerAction(QScrollBar::SliderSingleStepSub);
lines++;
}
}
void FileDiffWidget::onScrollValueChanged2(int value)
{
ui->verticalScrollBar->setValue(value);
}
void FileDiffWidget::onDiffWidgetResized()
{
updateControls();
}
void FileDiffWidget::on_toolButton_fullscreen_clicked()
{
if (m->init_param_.diff.blob.a_id.isEmpty() && m->init_param_.diff.blob.b_id.isEmpty()) {
return;
}
BigDiffWindow win(mainwindow());
win.setWindowState(Qt::WindowMaximized);
win.init(mainwindow(), m->init_param_);
win.exec();
}
void FileDiffWidget::setFocusAcceptable(Qt::FocusPolicy focuspolicy)
{
// Qt::FocusPolicy focuspolicy = f ? Qt::ClickFocus : Qt::NoFocus;
ui->widget_diff_left->setFocusPolicy(focuspolicy);
ui->widget_diff_right->setFocusPolicy(focuspolicy);
}
void FileDiffWidget::onUpdateSliderBar()
{
int total = m->engine_left->document.lines.size();
int value = ui->verticalScrollBar->value();
int page = ui->verticalScrollBar->pageStep();
ui->widget_diff_slider->setScrollPos(total, value, page);
}
void FileDiffWidget::refrectScrollBar()
{
ui->widget_diff_left->refrectScrollBar();
ui->widget_diff_right->refrectScrollBar();
onUpdateSliderBar();
}
QPixmap FileDiffWidget::makeDiffPixmap(DiffPane pane, int width, int height)
{
auto Do = [&](TextDiffLineList const &lines){
return FileDiffSliderWidget::makeDiffPixmap(width, height, lines, global->theme);
};
if (pane == DiffPane::Left) return Do(m->left_lines);
if (pane == DiffPane::Right) return Do(m->right_lines);
return QPixmap();
}
void FileDiffWidget::onMoved(int cur_row, int cur_col, int scr_row, int scr_col)
{
(void)cur_col;
(void)cur_row;
ui->widget_diff_left->move(-1, -1, scr_row, scr_col, false);
ui->widget_diff_right->move(-1, -1, scr_row, scr_col, false);
refrectScrollBar();
onUpdateSliderBar();
}
void FileDiffWidget::setTextCodec(QTextCodec *codec)
{
m->text_codec = codec;
ui->widget_diff_left->setTextCodec(codec);
ui->widget_diff_right->setTextCodec(codec);
emit textcodecChanged();
}
void FileDiffWidget::setTextCodec(char const *name)
{
QTextCodec *codec = name ? QTextCodec::codecForName(name) : nullptr;
setTextCodec(codec);
}
void FileDiffWidget::on_toolButton_menu_clicked()
{
QMenu menu;
QAction *a_utf8 = menu.addAction("UTF-8");
QAction *a_sjis = menu.addAction("SJIS (CP932)");
QAction *a_eucjp = menu.addAction("EUC-JP");
QAction *a_iso2022jp = menu.addAction("JIS (ISO-2022-JP)");
QAction *a = menu.exec(QCursor::pos() + QPoint(8, -8));
if (a) {
if (a == a_utf8) {
setTextCodec((char const *)nullptr);
return;
}
if (a == a_sjis) {
setTextCodec("Shift_JIS");
return;
}
if (a == a_eucjp) {
setTextCodec("EUC-JP");
return;
}
if (a == a_iso2022jp) {
setTextCodec("ISO-2022-JP");
return;
}
}
}
diff --git a/src/FileHistoryWindow.cpp b/src/FileHistoryWindow.cpp
index c00928c..6e6179a 100644
--- a/src/FileHistoryWindow.cpp
+++ b/src/FileHistoryWindow.cpp
@@ -1,250 +1,250 @@
#include "FileHistoryWindow.h"
#include "MainWindow.h"
#include "FileDiffWidget.h"
#include "GitDiff.h"
#include "MyTableWidgetDelegate.h"
#include "common/joinpath.h"
#include "common/misc.h"
#include "ui_FileHistoryWindow.h"
#include <QMenu>
#include <QPainter>
#include <QStyledItemDelegate>
#include <QThread>
struct FileHistoryWindow::Private {
GitPtr g;
QString path;
Git::CommitItemList commit_item_list;
FileDiffWidget::DiffData diff_data;
// FileDiffWidget::DrawData draw_data;
};
FileDiffWidget::DiffData *FileHistoryWindow::diffdata()
{
return &m->diff_data;
}
const FileDiffWidget::DiffData *FileHistoryWindow::diffdata() const
{
return &m->diff_data;
}
//FileDiffWidget::DrawData *FileHistoryWindow::drawdata()
//{
// return &m->draw_data;
//}
//const FileDiffWidget::DrawData *FileHistoryWindow::drawdata() const
//{
// return &m->draw_data;
//}
int FileHistoryWindow::totalTextLines() const
{
return diffdata()->left->lines.size();
}
//int FileHistoryWindow::fileviewScrollPos() const
//{
// return drawdata()->v_scroll_pos;
//}
FileHistoryWindow::FileHistoryWindow(BasicMainWindow *parent)
: QDialog(parent)
, ui(new Ui::FileHistoryWindow)
, m(new Private)
{
ui->setupUi(this);
ui->tableWidget_log->setItemDelegate(new MyTableWidgetDelegate(this));
Qt::WindowFlags flags = windowFlags();
flags &= ~Qt::WindowContextHelpButtonHint;
flags |= Qt::WindowMaximizeButtonHint;
setWindowFlags(flags);
ui->splitter->setSizes({100, 200});
ui->widget_diff_view->bind(mainwindow());
// connect(ui->widget_diff_view, &FileDiffWidget::moveNextItem, this, &FileHistoryWindow::onMoveNextItem);
// connect(ui->widget_diff_view, &FileDiffWidget::movePreviousItem, this, &FileHistoryWindow::onMovePreviousItem);
}
FileHistoryWindow::~FileHistoryWindow()
{
delete m;
delete ui;
}
MainWindow *FileHistoryWindow::mainwindow()
{
return qobject_cast<MainWindow *>(parent());
}
void FileHistoryWindow::prepare(GitPtr const &g, QString const &path)
{
Q_ASSERT(g);
Q_ASSERT(g->isValidWorkingCopy());
this->m->g = g;
this->m->path = path;
QString reponame = mainwindow()->currentRepositoryName();
QString brname = mainwindow()->currentBranch().name;
QString text = "%1 (%2)";
text = text.arg(reponame).arg(brname);
ui->label_repo->setText(text);
ui->label_path->setText(path);
{
OverrideWaitCursor;
m->commit_item_list = m->g->log_all(m->path, mainwindow()->limitLogCount());
}
collectFileHistory();
updateDiffView();
}
void FileHistoryWindow::collectFileHistory()
{
QStringList cols = {
tr("Commit"),
tr("Date"),
tr("Author"),
tr("Message"),
};
int n = cols.size();
ui->tableWidget_log->setColumnCount(n);
ui->tableWidget_log->setRowCount(0);
for (int i = 0; i < n; i++) {
QString const &text = cols[i];
auto *item = new QTableWidgetItem(text);
ui->tableWidget_log->setHorizontalHeaderItem(i, item);
}
int count = m->commit_item_list.size();
ui->tableWidget_log->setRowCount(count);
for (int row = 0; row < count; row++) {
Git::CommitItem const &commit = m->commit_item_list[row];
int col = 0;
auto AddColumn = [&](QString const &text, QString const &tooltip){
auto *item = new QTableWidgetItem(text);
item->setToolTip(tooltip);
ui->tableWidget_log->setItem(row, col, item);
col++;
};
QString commit_id = MainWindow::abbrevCommitID(commit);
QString datetime = misc::makeDateTimeString(commit.commit_date);
AddColumn(commit_id, QString());
AddColumn(datetime, QString());
AddColumn(commit.author, QString());
AddColumn(commit.message, commit.message);
ui->tableWidget_log->setRowHeight(row, 24);
}
ui->tableWidget_log->resizeColumnsToContents();
ui->tableWidget_log->horizontalHeader()->setStretchLastSection(false);
ui->tableWidget_log->horizontalHeader()->setStretchLastSection(true);
ui->tableWidget_log->setFocus();
ui->tableWidget_log->setCurrentCell(0, 0);
}
class FindFileIdThread : public QThread {
private:
MainWindow *mainwindow;
GitPtr g;
QString commit_id;
QString file;
public:
QString result;
FindFileIdThread(MainWindow *mw, GitPtr const &g, QString const &commit_id, QString const &file)
{
this->mainwindow = mw;
this->g = g;
this->commit_id = commit_id;
this->file = file;
}
protected:
void run() override
{
- result = mainwindow->findFileID(commit_id, file);
+ result = mainwindow->findFileID(mainwindow->frame(), commit_id, file);
}
};
void FileHistoryWindow::updateDiffView()
{
Q_ASSERT(m->g);
Q_ASSERT(m->g->isValidWorkingCopy());
ui->widget_diff_view->clearDiffView();
int row = ui->tableWidget_log->currentRow();
if (row >= 0 && row + 1 < (int)m->commit_item_list.size()) {
Git::CommitItem const &commit_left = m->commit_item_list[row + 1]; // older
Git::CommitItem const &commit_right = m->commit_item_list[row]; // newer
FindFileIdThread left_thread(mainwindow(), m->g->dup(), commit_left.commit_id, m->path);
FindFileIdThread right_thread(mainwindow(), m->g->dup(), commit_right.commit_id, m->path);
left_thread.start();
right_thread.start();
left_thread.wait();
right_thread.wait();
QString id_left = left_thread.result;
QString id_right = right_thread.result;
ui->widget_diff_view->updateDiffView(id_left, id_right, m->path);
} else if (row >= 0 && row < (int)m->commit_item_list.size()) {
Git::CommitItem const &commit = m->commit_item_list[row]; // newer
- QString id = mainwindow()->findFileID(commit.commit_id, m->path);
+ QString id = mainwindow()->findFileID(mainwindow()->frame(), commit.commit_id, m->path);
Git::Diff diff(id, m->path, QString());
ui->widget_diff_view->updateDiffView(diff, false);
}
}
void FileHistoryWindow::on_tableWidget_log_currentItemChanged(QTableWidgetItem * /*current*/, QTableWidgetItem * /*previous*/)
{
updateDiffView();
}
//void FileHistoryWindow::onMoveNextItem()
//{
// int row = ui->tableWidget_log->currentRow();
// int count = ui->tableWidget_log->rowCount();
// if (row + 1 < count) {
// ui->tableWidget_log->setCurrentCell(row + 1, 0, QItemSelectionModel::ClearAndSelect);
// }
//}
//void FileHistoryWindow::onMovePreviousItem()
//{
// int row = ui->tableWidget_log->currentRow();
// if (row > 0) {
// ui->tableWidget_log->setCurrentCell(row - 1, 0, QItemSelectionModel::ClearAndSelect);
// }
//}
void FileHistoryWindow::on_tableWidget_log_customContextMenuRequested(const QPoint &pos)
{
(void)pos;
Git::CommitItem const *commit = nullptr;
int row = ui->tableWidget_log->currentRow();
if (row >= 0 && row < (int)m->commit_item_list.size()) {
commit = &m->commit_item_list[row];
}
if (!commit) return;
QMenu menu;
QAction *a_property = mainwindow()->addMenuActionProperty(&menu);
QAction *a = menu.exec(QCursor::pos() + QPoint(8, -8));
if (a) {
if (a == a_property) {
mainwindow()->execCommitPropertyDialog(this, mainwindow()->frame(), commit);
return;
}
}
}
diff --git a/src/FilesListWidget.cpp b/src/FilesListWidget.cpp
index 6d9d0e1..581f46a 100644
--- a/src/FilesListWidget.cpp
+++ b/src/FilesListWidget.cpp
@@ -1,115 +1,115 @@
#include "FilesListWidget.h"
#include "BasicMainWindow.h"
#include <QDebug>
#include <QPainter>
#include <QStyledItemDelegate>
#include <map>
namespace {
class ItemDelegate : public QStyledItemDelegate {
private:
struct Badge {
QString text;
QColor color;
QIcon icon;
Badge() = default;
Badge(QString const &text, QColor const &color, QIcon const &icon)
: text(text)
, color(color)
, icon(icon)
{
}
};
std::map<QString, Badge> badge_map;
public:
ItemDelegate(QObject *parent)
: QStyledItemDelegate(parent)
{
badge_map["(chg) "] = Badge("Chg", QColor(240, 240, 140), QIcon(":/image/chg.svg"));
badge_map["(add) "] = Badge("Add", QColor(180, 240, 180), QIcon(":/image/add.svg"));
badge_map["(del) "] = Badge("Del", QColor(255, 200, 200), QIcon(":/image/del.svg"));
badge_map["(ren) "] = Badge("Ren", QColor(200, 210, 255), QIcon(":/image/ren.svg"));
badge_map["(cpy) "] = Badge("Cpy", QColor(200, 210, 255), QIcon(":/image/cpy.svg"));
}
/**
* @brief ファイルリストの1行分を描画する
* @param painter
* @param option
* @param index
*/
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
{
QStyleOptionViewItem o = option;
QStyledItemDelegate::initStyleOption(&o, index);
QString header = index.data(BasicMainWindow::HeaderRole).toString();
- bool isSubmodule = index.data(BasicMainWindow::IsSubmoduleRole).toBool();
+// bool isSubmodule = !index.data(BasicMainWindow::SubmodulePathRole).toString().isEmpty();
int x = o.rect.x();
int y = o.rect.y();
int h = o.rect.height();
int w = 2 + h + painter->fontMetrics().size(0, " Aaa").width() + 2;
// バッジの描画
Badge badge;
if (header == "(unmerged) ") {
badge = Badge("Unmerged", QColor(255, 80, 160), QIcon());
w = 4 + painter->fontMetrics().size(0, badge.text).width() + 4;
}
{
QColor color;
auto it = badge_map.find(header);
if (it != badge_map.end()) {
badge = it->second;
color = badge.color;
} else {
color = badge.color.isValid() ? badge.color : QColor(160, 160, 160);
}
{
QRect r(x, y, w, h);
QRect r_icon = badge.icon.isNull() ? QRect() : QRect(x + 2, y + 1, h - 2, h - 2).adjusted(2, 2, -2, -2);
QRect r_badge = r.adjusted(1, 1, -2, -2);
QRect r_text = r.adjusted(r_icon.width(), 0, 0, 0);
painter->setPen(Qt::NoPen);
painter->setBrush(QBrush(color.darker(130)));
painter->drawRoundedRect(r_badge.translated(1, 1), 3, 3);
painter->setBrush(QBrush(color));
painter->drawRoundedRect(r_badge, 3, 3);
// アイコン描画
if (!badge.icon.isNull()) {
painter->save();
painter->setOpacity(0.5);
badge.icon.paint(painter, r_icon);
painter->restore();
}
painter->setPen(Qt::black);
QTextOption to;
to.setAlignment(Qt::AlignCenter | Qt::AlignVCenter);
if (badge.text.isEmpty()) {
badge.text = "?";
}
// バッジテキスト描画
painter->drawText(r_text, badge.text, to);
}
}
o.rect.adjust(w, 0, 0, 0);
#if 0
// サブモジュール
if (isSubmodule) {
o.rect.adjust(h, 0, 0, 0);
}
#endif
// アイテムテキスト描画
option.widget->style()->drawControl(QStyle::CE_ItemViewItem, &o, painter, option.widget);
}
};
}
FilesListWidget::FilesListWidget(QWidget *parent)
: QListWidget(parent)
{
item_delegate = new ItemDelegate(this);
setItemDelegate(item_delegate);
}
diff --git a/src/Git.cpp b/src/Git.cpp
index c8b3e67..4c5ea6a 100644
--- a/src/Git.cpp
+++ b/src/Git.cpp
@@ -1,1619 +1,1625 @@
#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 {
- QString git_command;
+ 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;
- QString working_repo_dir;
- callback_t fn_log_writer_callback = nullptr;
- void *callback_cookie = nullptr;
};
Git::Git()
: m(new Private)
{
}
-Git::Git(const Context &cx, QString const &repodir, const QString &sshkey)
+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, sshkey);
+ setWorkingRepositoryDir(repodir, submodpath, sshkey);
}
Git::~Git()
{
delete m;
}
void Git::setLogCallback(callback_t func, void *cookie)
{
- m->fn_log_writer_callback = func;
- m->callback_cookie = cookie;
+ m->info.fn_log_writer_callback = func;
+ m->info.callback_cookie = cookie;
}
-void Git::setWorkingRepositoryDir(QString const &repo, QString const &sshkey)
+void Git::setWorkingRepositoryDir(QString const &repo, const QString &submodpath, QString const &sshkey)
{
- m->working_repo_dir = repo;
+ m->info.working_repo_dir = repo;
+ m->info.submodule_path = submodpath;
m->ssh_key_override = sshkey;
}
-QString const &Git::workingRepositoryDir() const
+QString Git::workingDir() const
{
- return m->working_repo_dir;
+ 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->git_command = gitcmd;
+ m->info.git_command = gitcmd;
m->ssh_command = sshcmd;
}
QString Git::gitCommand() const
{
Q_ASSERT(m);
- return m->git_command;
+ 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 = workingRepositoryDir();
+ 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->fn_log_writer_callback) {
+ if (m->info.fn_log_writer_callback) {
QByteArray ba;
ba.append("> git ");
ba.append(arg);
ba.append('\n');
- m->fn_log_writer_callback(m->callback_cookie, ba.data(), (int)ba.size());
+ 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(workingRepositoryDir());
+ 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->git_command = m->git_command;
- p->m->working_repo_dir = m->working_repo_dir;
- p->m->fn_log_writer_callback = m->fn_log_writer_callback;
- p->m->callback_cookie = m->callback_cookie;
+ 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(workingRepositoryDir());
+ return isValidWorkingCopy(workingDir());
}
QString Git::version()
{
git("--version", false);
return resultText().trimmed();
}
bool Git::init()
{
bool ok = false;
QDir cwd = QDir::current();
- QString dir = workingRepositoryDir();
+ 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->working_repo_dir = misc::normalizePathSeparator(clone_to);
+ 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 = workingRepositoryDir();
+ 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)
{
QString cmd = "merge ";
switch (ff) {
case MergeFastForward::No:
cmd += "--no-ff ";
break;
case MergeFastForward::Only:
cmd += "--ff-only ";
break;
default:
cmd += "--ff ";
break;
}
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 543fc9d..35f813f 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);
+ 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 &sshkey = {});
+ 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 &sshkey);
- QString const &workingRepositoryDir() const;
+ 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 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/GitObjectManager.cpp b/src/GitObjectManager.cpp
index 2118c7e..8cf801a 100644
--- a/src/GitObjectManager.cpp
+++ b/src/GitObjectManager.cpp
@@ -1,438 +1,438 @@
#include "GitObjectManager.h"
#include "Git.h"
#include "common/joinpath.h"
#include "common/misc.h"
#include <QBuffer>
#include <QDebug>
#include <QDirIterator>
#include <QFile>
#include <memory>
GitObjectManager::GitObjectManager()
{
subdir_git_objects = ".git/objects";
subdir_git_objects_pack = subdir_git_objects / "pack";
}
void GitObjectManager::setup(GitPtr const &g)
{
this->g = g;
clearIndexes();
}
QString GitObjectManager::workingDir()
{
- return g->workingRepositoryDir();
+ return g->workingDir();
}
void GitObjectManager::loadIndexes()
{
QMutexLocker lock(&mutex);
if (git_idx_list.empty()) {
QString path = workingDir() / subdir_git_objects_pack;
QDirIterator it(path, { "pack-*.idx" }, QDir::Files | QDir::Readable);
while (it.hasNext()) {
it.next();
GitPackIdxPtr idx = std::make_shared<GitPackIdxV2>();
idx->basename = it.fileInfo().baseName();
idx->parse(it.filePath());
git_idx_list.push_back(idx);
}
}
}
void GitObjectManager::clearIndexes()
{
git_idx_list.clear();
}
void GitObjectManager::applyDelta(QByteArray const *base_obj, QByteArray const *delta_obj, QByteArray *out)
{
if (delta_obj->size() > 0) {
uint8_t const *begin = (uint8_t const *)delta_obj->data();
uint8_t const *end = begin + delta_obj->size();
uint8_t const *ptr = begin;
auto ReadNumber = [&](){
uint64_t n = 0;
int shift = 0;
while (ptr < end) {
uint64_t c = *ptr;
ptr++;
n |= (c & 0x7f) << shift;
shift += 7;
if (!(c & 0x80)) break;
}
return n;
};
uint64_t a = ReadNumber(); // older file size
uint64_t b = ReadNumber(); // newer file size
(void)a;
(void)b;
// cf. https://github.com/github/git-msysgit/blob/master/patch-delta.c
while (ptr < end) {
uint8_t op = *ptr;
ptr++;
if (op & 0x80) { // copy operation
uint32_t offset = 0;
uint32_t length = 0;
if (op & 0x01) offset = *ptr++;
if (op & 0x02) offset |= (*ptr++ << 8);
if (op & 0x04) offset |= (*ptr++ << 16);
if (op & 0x08) offset |= ((unsigned) *ptr++ << 24);
if (op & 0x10) length = *ptr++;
if (op & 0x20) length |= (*ptr++ << 8);
if (op & 0x40) length |= (*ptr++ << 16);
if (length == 0) length = 0x10000;
if (offset + length > (uint32_t)base_obj->size()) {
qDebug() << Q_FUNC_INFO << "base-file or delta-file is corrupted";
out->clear();
return;
}
out->append(base_obj->data() + offset, length);
} else if (op > 0) { // insert operation
int length = op & 0x7f;
if (ptr + length <= end) {
out->append((char const *)ptr, length);
ptr += length;
}
} else {
qDebug() << Q_FUNC_INFO << "unexpected delta opcode 0";
}
}
}
}
bool GitObjectManager::loadPackedObject(GitPackIdxPtr const &idx, QIODevice *packfile, GitPackIdxItem const *item, GitPack::Object *out)
{
GitPack::Info info;
if (GitPack::seekPackedObject(packfile, item, &info)) {
GitPackIdxItem const *source_item = nullptr;
if (info.type == Git::Object::Type::OFS_DELTA) {
source_item = idx->item(item->offset - info.offset);
} else if (info.type == Git::Object::Type::REF_DELTA) {
source_item = idx->item(info.ref_id);
}
if (source_item) { // if deltified object
GitPack::Object source;
if (source_item && loadPackedObject(idx, packfile, source_item, &source)) {
GitPack::Object delta;
if (GitPack::load(packfile, item, &delta)) {
if (delta.checksum != item->checksum) {
qDebug() << "crc checksum incorrect";
return false;
}
QByteArray ba;
applyDelta(&source.content, &delta.content, &ba);
*out = GitPack::Object();
out->type = source.type;
out->content = std::move(ba);
out->expanded_size = out->content.size();
return true;
}
}
qDebug() << Q_FUNC_INFO << "failed";
return false;
}
}
return GitPack::load(packfile, item, out);
}
bool GitObjectManager::extractObjectFromPackFile(GitPackIdxPtr const &idx, GitPackIdxItem const *item, GitPack::Object *out)
{
*out = GitPack::Object();
QString packfilepath = workingDir() / subdir_git_objects_pack / (idx->basename + ".pack");
QFile packfile(packfilepath);
if (packfile.open(QFile::ReadOnly)) {
if (loadPackedObject(idx, &packfile, item, out)) {
if (out->type == Git::Object::Type::TREE) {
GitPack::decodeTree(&out->content);
}
return true;
}
}
return false;
}
bool GitObjectManager::extractObjectFromPackFile(QString const &id, QByteArray *out, Git::Object::Type *type)
{
loadIndexes();
for (GitPackIdxPtr const &idx : git_idx_list) {
GitPackIdxItem const *item = idx->item(id);
if (item) {
GitPack::Object obj;
if (extractObjectFromPackFile(idx, item, &obj)) {
*out = std::move(obj.content);
*type = obj.type;
return true;
}
qDebug() << Q_FUNC_INFO << "failed";
return false;
}
}
return false;
}
QString GitObjectManager::findObjectPath(QString const &id)
{
if (Git::isValidID(id)) {
int count = 0;
QString absolute_path;
QString xx = id.mid(0, 2); // leading two xdigits
QString name = id.mid(2); // remaining xdigits
QString dir = workingDir() / subdir_git_objects / xx; // e.g. /home/user/myproject/.git/objects/5a
QDirIterator it(dir, QDir::Files);
while (it.hasNext()) {
it.next();
if (it.fileName().startsWith(name)) {
QString id = xx + it.fileName(); // complete id
if (id.size() == GIT_ID_LENGTH && Git::isValidID(id)) {
absolute_path = dir / it.fileName();
count++;
}
}
}
if (count == 1) return absolute_path;
if (count > 1) qDebug() << Q_FUNC_INFO << "ambiguous id" << id;
}
return QString(); // not found
}
bool GitObjectManager::loadObject(QString const &id, QByteArray *out, Git::Object::Type *type)
{
QString path = findObjectPath(id);
if (!path.isEmpty()) {
QFile file(path);
if (file.open(QFile::ReadOnly)) {
if (GitPack::decompress(&file, 1000000000, out)) {
*type = GitPack::stripHeader(out);
if (*type == Git::Object::Type::TREE) {
GitPack::decodeTree(out);
}
return true;
}
}
}
return false;
}
bool GitObjectManager::catFile(QString const &id, QByteArray *out, Git::Object::Type *type)
{
*type = Git::Object::Type::UNKNOWN;
if (loadObject(id, out, type)) return true;
if (extractObjectFromPackFile(id, out, type)) return true;
return false;
}
//
size_t GitObjectCache::size() const
{
size_t size = 0;
for (ItemPtr const &item : items) {
size += item->ba.size();
}
return size;
}
void GitObjectCache::setup(GitPtr const &g)
{
items.clear();
revparsemap.clear();
if (g) {
object_manager.setup(g->dup());
}
}
QString GitObjectCache::revParse(QString const &name)
{
GitPtr g = git();
if (!g) return QString();
{
QMutexLocker lock(&object_manager.mutex);
auto it = revparsemap.find(name);
if (it != revparsemap.end()) {
return it->second;
}
}
QString id = g->rev_parse(name);
{
QMutexLocker lock(&object_manager.mutex);
revparsemap[name] = id;
return id;
}
}
Git::Object GitObjectCache::catFile(QString const &id)
{
{
QMutexLocker lock(&object_manager.mutex);
size_t n = items.size();
size_t i = n;
while (i > 0) {
i--;
if (items[i]->id == id) {
ItemPtr item = items[i];
if (i + 1 < n) {
items.erase(items.begin() + i);
items.push_back(item);
}
Git::Object obj;
obj.type = item->type;
obj.content = item->ba;
return obj;
}
}
while (size() > 100000000) { // 100MB
items.erase(items.begin());
}
}
QByteArray ba;
Git::Object::Type type = Git::Object::Type::UNKNOWN;
auto Store = [&](){
QMutexLocker lock(&object_manager.mutex);
Item *item = new Item();
item->id = id;
item->ba = std::move(ba);
item->type = type;
items.push_back(ItemPtr(item));
Git::Object obj;
obj.type = item->type;
obj.content = item->ba;
return obj;
};
if (object_manager.catFile(id, &ba, &type)) { // 独自実装のファイル取得
return Store();
}
if (true) {
if (git()->cat_file(id, &ba)) { // 外部コマンド起動の git cat-file -p を試してみる
// 上の独自実装のファイル取得が正しく動作していれば、ここには来ないはず
qDebug() << __FILE__ << __LINE__ << Q_FUNC_INFO << id;
return Store();
}
}
qDebug() << "failed to cat file: " << id;
return Git::Object();
}
QString GitObjectCache::getCommitIdFromTag(QString const &tag)
{
QString commit_id;
GitPtr g = git();
if (g && g->isValidWorkingCopy()) {
QString id = g->rev_parse(tag);
Git::Object obj = catFile(id);
switch (obj.type) {
case Git::Object::Type::COMMIT:
commit_id = id;
break;
case Git::Object::Type::TAG:
if (!obj.content.isEmpty()) {
misc::splitLines(obj.content, [&](char const *ptr, size_t len){
if (commit_id.isEmpty()) {
if (len >= 7 + GIT_ID_LENGTH && strncmp(ptr, "object ", 7) == 0) {
QString id = QString::fromUtf8(ptr + 7, len - 7).trimmed();
if (Git::isValidID(id)) {
commit_id = id;
}
}
}
return QString();
});
}
break;
}
}
return commit_id;
}
bool GitCommit::parseCommit(GitObjectCache *objcache, QString const &id, GitCommit *out)
{
*out = {};
if (!id.isEmpty()) {
QStringList parents;
{
Git::Object obj = objcache->catFile(id);
if (!obj.content.isEmpty()) {
QStringList lines = misc::splitLines(QString::fromUtf8(obj.content));
for (QString const &line : lines) {
int i = line.indexOf(' ');
if (i < 1) break;
QString key = line.mid(0, i);
QString val = line.mid(i + 1).trimmed();
if (key == "tree") {
out->tree_id = val;
} else if (key == "parent") {
parents.push_back(val);
}
}
}
}
if (!out->tree_id.isEmpty()) { // サブディレクトリ
out->parents.append(parents);
return true;
}
}
return false;
}
void parseGitTreeObject(QByteArray const &ba, const QString &path_prefix, GitTreeItemList *out)
{
*out = {};
QString s = QString::fromUtf8(ba);
QStringList lines = misc::splitLines(s);
for (QString const &line : lines) {
int tab = line.indexOf('\t'); // タブより後ろにパスがある
if (tab > 0) {
QString stat = line.mid(0, tab); // タブの手前まで
QStringList vals = misc::splitWords(stat); // 空白で分割
if (vals.size() >= 3) {
GitTreeItem data;
data.mode = vals[0]; // ファイルモード
data.id = vals[2]; // id(ハッシュ値)
QString type = vals[1]; // 種類(tree/blob)
QString path = line.mid(tab + 1); // パス
path = Git::trimPath(path);
data.name = path_prefix.isEmpty() ? path : misc::joinWithSlash(path_prefix, path);
if (type == "tree") {
data.type = GitTreeItem::TREE;
} else if (type == "blob") {
data.type = GitTreeItem::BLOB;
} else if (type == "commit") {
data.type = GitTreeItem::COMMIT;
}
if (data.type != GitTreeItem::UNKNOWN) {
out->push_back(data);
}
}
}
}
}
bool parseGitTreeObject(GitObjectCache *objcache, const QString &commit_id, const QString &path_prefix, GitTreeItemList *out)
{
out->clear();
if (!commit_id.isEmpty()) {
Git::Object obj = objcache->catFile(commit_id);
if (!obj.content.isEmpty()) { // 内容を取得
parseGitTreeObject(obj.content, path_prefix, out);
return true;
}
}
return false;
}
diff --git a/src/GitObjectManager.h b/src/GitObjectManager.h
index 75e83ac..26a9250 100644
--- a/src/GitObjectManager.h
+++ b/src/GitObjectManager.h
@@ -1,145 +1,145 @@
#ifndef GITOBJECTMANAGER_H
#define GITOBJECTMANAGER_H
#include <QMutex>
#include <QString>
#include "GitPack.h"
#include "GitPackIdxV2.h"
#include <map>
#include "common/joinpath.h"
class GitPackIdxV2;
class Git;
using GitPtr = std::shared_ptr<Git>;
class GitObjectManager {
friend class GitObjectCache;
private:
GitPtr g;
QMutex mutex;
QString subdir_git_objects;
QString subdir_git_objects_pack;
std::vector<GitPackIdxPtr> git_idx_list;
QString workingDir();
static void applyDelta(QByteArray const *base, QByteArray const *delta, QByteArray *out);
static bool loadPackedObject(GitPackIdxPtr const &idx, QIODevice *packfile, GitPackIdxItem const *item, GitPack::Object *out);
bool extractObjectFromPackFile(GitPackIdxPtr const &idx, GitPackIdxItem const *item, GitPack::Object *out);
bool extractObjectFromPackFile(QString const &id, QByteArray *out, Git::Object::Type *type);
void loadIndexes();
QString findObjectPath(QString const &id);
bool loadObject(QString const &id, QByteArray *out, Git::Object::Type *type);
GitPtr git()
{
return g->dup();
}
GitPtr git(Git::SubmoduleItem const &submod)
{
GitPtr g2 = g->dup();
- g2->setWorkingRepositoryDir(g->workingRepositoryDir() / submod.path, g->sshKey());
+ g2->setWorkingRepositoryDir(g->workingDir(), submod.path, g->sshKey());
return g2;
}
public:
GitObjectManager();
void setup(const GitPtr &g);
bool catFile(QString const &id, QByteArray *out, Git::Object::Type *type);
void clearIndexes();
};
class GitObjectCache {
public:
struct Item {
QString id;
QByteArray ba;
Git::Object::Type type;
};
private:
GitObjectManager object_manager;
using ItemPtr = std::shared_ptr<Item>;
std::vector<ItemPtr> items;
std::map<QString, QString> revparsemap;
size_t size() const;
public:
GitPtr git()
{
return object_manager.git();
}
GitPtr git(Git::SubmoduleItem const &submod)
{
return object_manager.git(submod);
}
void setup(const GitPtr &g);
QString revParse(QString const &name);
Git::Object catFile(QString const &id);
QString getCommitIdFromTag(QString const &tag);
};
class GitCommit {
public:
QString tree_id;
QStringList parents;
static bool parseCommit(GitObjectCache *objcache, QString const &id, GitCommit *out);
};
struct GitTreeItem {
enum Type {
UNKNOWN,
TREE,
BLOB,
COMMIT,
};
Type type = UNKNOWN;
QString name;
QString id;
QString mode;
QString to_string_() const
{
QString t;
switch (type) {
case TREE: t = "TREE"; break;
case BLOB: t = "BLOB"; break;
}
return QString("GitTreeItem:{ %1 %2 %3 %4 }").arg(t).arg(id).arg(mode).arg(name);
}
};
using GitTreeItemList = QList<GitTreeItem>;
class GitCommitTree {
private:
GitObjectCache *objcache;
GitTreeItemList root_item_list;
std::map<QString, GitTreeItem> blob_map;
std::map<QString, QString> tree_id_map;
GitPtr git();
QString lookup_(QString const &file, GitTreeItem *out);
public:
GitCommitTree(GitObjectCache *objcache);
QString lookup(QString const &file);
bool lookup(QString const &file, GitTreeItem *out);
void parseTree(QString const &tree_id);
QString parseCommit(QString const &commit_id);
GitTreeItemList const *treelist() const
{
return &root_item_list;
}
};
QString lookupFileID(GitObjectCache *objcache, QString const &commit_id, QString const &file);
void parseGitTreeObject(QByteArray const &ba, const QString &path_prefix, GitTreeItemList *out);
bool parseGitTreeObject(GitObjectCache *objcache, QString const &commit_id, QString const &path_prefix, GitTreeItemList *out);
#endif // GITOBJECTMANAGER_H
diff --git a/src/LogTableWidget.cpp b/src/LogTableWidget.cpp
index 5448041..8908922 100644
--- a/src/LogTableWidget.cpp
+++ b/src/LogTableWidget.cpp
@@ -1,406 +1,404 @@
#include "LogTableWidget.h"
#include "MainWindow.h"
#include "MyTableWidgetDelegate.h"
#include "common/misc.h"
#include <QApplication>
#include <QDebug>
#include <QEvent>
#include <QPainter>
#include <QPainterPath>
#include <QProxyStyle>
#include <cmath>
#include <map>
#include "ApplicationGlobal.h"
#include "RepositoryWrapperFrame.h"
struct LogTableWidget::Private {
RepositoryWrapperFrame *frame = nullptr;
};
/**
* @brief コミットログを描画するためのdelegate
*/
class LogTableWidgetDelegate : public MyTableWidgetDelegate {
private:
- RepositoryWrapperFrame *mainwindow() const
+ RepositoryWrapperFrame *frame() const
{
auto *w = dynamic_cast<LogTableWidget *>(QStyledItemDelegate::parent());
Q_ASSERT(w);
return w->frame();
}
static QColor hiliteColor(QColor const &color)
{
int r = color.red();
int g = color.green();
int b = color.blue();
r = 255 - (255 - r) / 2;
g = 255 - (255 - g) / 2;
b = 255 - (255 - b) / 2;
return QColor(r, g, b);
}
static QColor shadowColor(QColor const &color)
{
return QColor(color.red() / 2, color.green() / 2, color.blue() / 2);
}
void drawSignatureIcon(QPainter *painter, const QStyleOptionViewItem &opt, QModelIndex const &index) const
{
if (!opt.widget->isEnabled()) return;
- Git::CommitItem const *commit = mainwindow()->commitItem(index.row());
+ Git::CommitItem const *commit = frame()->commitItem(index.row());
if (commit) {
- QIcon icon = mainwindow()->verifiedIcon(commit->signature);
+ QIcon icon = frame()->verifiedIcon(commit->signature);
if (!icon.isNull()) {
QRect r = opt.rect.adjusted(6, 3, 0, -3);
int h = r.height();
int w = h;
int x = r.x() + r.width() - w;
int y = r.y();
icon.paint(painter, x, y, w, h);
}
}
}
void drawAvatar(QPainter *painter, const QStyleOptionViewItem &opt, QModelIndex const &index) const
{
if (!opt.widget->isEnabled()) return;
int row = index.row();
- QIcon icon = mainwindow()->committerIcon(row);
+ QIcon icon = frame()->committerIcon(row);
if (!icon.isNull()) {
int h = opt.rect.height();
int w = h;
int x = opt.rect.x() + opt.rect.width() - w;
int y = opt.rect.y();
painter->save();
painter->setOpacity(0.5); // 半透明で描画
icon.paint(painter, x, y, w, h);
painter->restore();
}
}
void drawLabels(QPainter *painter, const QStyleOptionViewItem &opt, QModelIndex const &index, QString const &current_branch) const
{
int row = index.row();
- QList<BranchLabel> const *labels = mainwindow()->label(row);
+ QList<BranchLabel> const *labels = frame()->label(row);
if (labels) {
painter->save();
painter->setRenderHint(QPainter::Antialiasing);
bool show = global->mainwindow->isLabelsVisible(); // ラベル透過モード
painter->setOpacity(show ? 1.0 : 0.0625);
const int space = 8;
int x = opt.rect.x() + opt.rect.width() - 3;
int x1 = x;
int y0 = opt.rect.y();
int y1 = y0 + opt.rect.height() - 1;
int i = labels->size();
while (i > 0) {
i--;
// ラベル
BranchLabel const &label = labels->at(i);
QString text = misc::abbrevBranchName(label.text + label.info);
// 現在のブランチ名と一致するなら太字
bool bold = false;
if (text.startsWith(current_branch)) {
auto c = text.utf16()[current_branch.size()];
if (c == 0 || c == ',') {
bold = true;
}
}
// フォントの設定
QFont font = painter->font();
font.setBold(bold);
painter->setFont(font);
// ラベルの矩形
int w = painter->fontMetrics().size(0, text).width() + space * 2; // 幅
int x0 = x1 - w;
QRect r(x0, y0, x1 - x0, y1 - y0);
// ラベル枠の描画
auto DrawLabelFrame = [&](int dx, int dy, QColor color){
painter->setBrush(color);
painter->drawRoundedRect(r.adjusted(lround(dx + 3), lround(dy + 3), lround(dx - 3), lround(dy - 3)), 3, 3);
};
QColor color = BranchLabel::color(label.kind); // ラベル表面の色
QColor hilite = hiliteColor(color); // ハイライトの色
QColor shadow = shadowColor(color); // 陰の色
painter->setPen(Qt::NoPen);
DrawLabelFrame(-1, -1, hilite);
DrawLabelFrame(1, 1, shadow);
DrawLabelFrame(0, 0, color);
// ラベルテキストの描画
painter->setPen(Qt::black);
painter->setBrush(Qt::NoBrush);
QApplication::style()->drawItemText(painter, r.adjusted(space, 0, 0, 0), opt.displayAlignment, opt.palette, true, text);
x1 = x0;
}
painter->restore();
}
}
public:
explicit LogTableWidgetDelegate(QObject *parent = Q_NULLPTR)
: MyTableWidgetDelegate(parent)
{
}
void paint(QPainter *painter, const QStyleOptionViewItem &option, QModelIndex const &index) const override
{
MyTableWidgetDelegate::paint(painter, option, index);
- RepositoryWrapperFrame *mw = mainwindow();
-
enum {
Graph,
CommitId,
Date,
Author,
Message,
};
// signatureの描画
if (index.column() == CommitId) {
drawSignatureIcon(painter, option, index);
}
// コミット日時
if (index.column() == Date) {
- Git::CommitItem const *commit = mw->commitItem(index.row());
+ Git::CommitItem const *commit = frame()->commitItem(index.row());
if (commit && commit->strange_date) {
QColor color(255, 0, 0, 128);
QRect r = option.rect.adjusted(1, 1, -1, -2);
misc::drawFrame(painter, r.x(), r.y(), r.width(), r.height(), color, color);
}
}
// avatarの描画
if (index.column() == Author) {
drawAvatar(painter, option, index);
}
// ラベルの描画
if (index.column() == Message) {
- QString current_branch = mw->currentBranchName();
+ QString current_branch = frame()->currentBranchName();
drawLabels(painter, option, index, current_branch);
}
}
};
LogTableWidget::LogTableWidget(QWidget *parent)
: QTableWidget(parent)
, m(new Private)
{
setItemDelegate(new LogTableWidgetDelegate(this));
}
LogTableWidget::~LogTableWidget()
{
delete m;
}
void LogTableWidget::bind(RepositoryWrapperFrame *frame)
{
m->frame = frame;
}
RepositoryWrapperFrame *LogTableWidget::frame()
{
// auto *mw = qobject_cast<RepositoryWrapperFrame *>(window());
// Q_ASSERT(mw);
// return mw;
Q_ASSERT(m->frame);
return m->frame;
}
void drawBranch(QPainterPath *path, double x0, double y0, double x1, double y1, double r, bool bend_early)
{
const double k = 0.55228475; // 三次ベジェ曲線で円を近似するための定数
if (x0 == x1) {
path->moveTo(x0, y0);
path->lineTo(x1, y1);
} else {
double ym = bend_early ? (y0 + r) : (y1 - r);
double h = fabs(y1 - y0);
double w = fabs(x1 - x0);
if (r > h / 2) r = h / 2;
if (r > w / 2) r = w / 2;
double s = r;
if (x0 > x1) r = -r;
if (y0 > y1) s = -s;
if (0) {
path->moveTo(x0, y0);
path->lineTo(x0, ym - s);
path->cubicTo(x0, ym - s + s * k, x0 + r - r * k, ym, x0 + r, ym);
path->lineTo(x1 - r, ym);
path->cubicTo(x1 - r + r * k, ym, x1, ym + s - s * k, x1, ym + s);
path->lineTo(x1, y1);
} else {
if (bend_early) {
path->moveTo(x0, y0);
path->cubicTo(x0, ym, x1, ym, x1, ym + ym - y0);
path->lineTo(x1, y1);
} else {
path->moveTo(x0, y0);
path->lineTo(x0, ym + ym - y1);
path->cubicTo(x0, ym, x1, ym, x1, y1);
}
}
}
}
void LogTableWidget::paintEvent(QPaintEvent *e)
{
if (rowCount() < 1) return;
QTableWidget::paintEvent(e);
QPainter pr(viewport());
pr.setRenderHint(QPainter::Antialiasing);
pr.setBrush(QBrush(QColor(255, 255, 255)));
Git::CommitItemList const *list = &frame()->getLogs();
int indent_span = 16;
int line_width = 2;
int thick_line_width = 4;
auto ItemRect = [&](int row){
QRect r;
QTableWidgetItem *p = item(row, 0);
if (p) {
r = visualItemRect(p);
}
return r;
};
auto IsAncestor = [&](Git::CommitItem const &item){
return frame()->isAncestorCommit(item.commit_id);
};
auto ItemPoint = [&](int depth, QRect const &rect){
int h = rect.height();
double n = h / 2.0;
double x = floor(rect.x() + n + depth * indent_span);
double y = floor(rect.y() + n);
return QPointF(x, y);
};
auto SetPen = [&](QPainter *pr, int level, bool thick){
QColor c = frame()->color(level + 1);
Qt::PenStyle s = Qt::SolidLine;
pr->setPen(QPen(c, thick ? thick_line_width : line_width, s));
};
auto DrawLine = [&](size_t index, int itemrow){
QRect rc1;
if (index < list->size()) {
Git::CommitItem const &item1 = list->at(index);
rc1 = ItemRect(itemrow);
QPointF pt1 = ItemPoint(item1.marker_depth, rc1);
double halfheight = rc1.height() / 2.0;
for (TreeLine const &line : item1.parent_lines) {
if (line.depth >= 0) {
QPainterPath *path = nullptr;
Git::CommitItem const &item2 = list->at(line.index);
QRect rc2 = ItemRect(line.index);
if (index + 1 == (size_t)line.index || line.depth == item1.marker_depth || line.depth == item2.marker_depth) {
QPointF pt2 = ItemPoint(line.depth, rc2);
if (pt2.y() > 0) {
path = new QPainterPath();
drawBranch(path, pt1.x(), pt1.y(), pt2.x(), pt2.y(), halfheight, line.bend_early);
}
} else {
QPointF pt3 = ItemPoint(item2.marker_depth, rc2);
if (pt3.y() > 0) {
path = new QPainterPath();
QRect rc3 = ItemRect(itemrow + 1);
QPointF pt2 = ItemPoint(line.depth, rc3);
drawBranch(path, pt1.x(), pt1.y(), pt2.x(), pt2.y(), halfheight, true);
drawBranch(path, pt2.x(), pt2.y(), pt3.x(), pt3.y(), halfheight, false);
}
}
if (path) {
SetPen(&pr, line.color_number, IsAncestor(item1));
pr.drawPath(*path);
delete path;
}
}
}
}
return rc1.y();
};
auto DrawMark = [&](size_t index, int row){
double x, y;
y = 0;
if (index < list->size()) {
Git::CommitItem const &item = list->at(index);
QRect rc = ItemRect(row);
QPointF pt = ItemPoint(item.marker_depth, rc);
double r = 4;
x = pt.x() - r;
y = pt.y() - r;
if (item.resolved) {
// ◯
SetPen(&pr, item.marker_depth, IsAncestor(item));
pr.drawEllipse((int)x, (int)y, int(r * 2), int(r * 2));
} else {
// ▽
SetPen(&pr, item.marker_depth, false);
QPainterPath path;
path.moveTo(pt.x(), pt.y() + r);
path.lineTo(pt.x() - r, pt.y() - r);
path.lineTo(pt.x() + r, pt.y() - r);
path.lineTo(pt.x(), pt.y() + r);
pr.drawPath(path);
}
}
return y;
};
// draw lines
pr.setOpacity(0.5);
pr.setBrush(Qt::NoBrush);
for (size_t i = 0; i < list->size(); i++) {
double y = DrawLine(i, i);
if (y >= height()) break;
}
// draw marks
pr.setOpacity(1);
pr.setBrush(frame()->color(0));
for (size_t i = 0; i < list->size(); i++) {
double y = DrawMark(i, i);
if (y >= height()) break;
}
}
void LogTableWidget::resizeEvent(QResizeEvent *e)
{
frame()->updateAncestorCommitMap();
QTableWidget::resizeEvent(e);
}
void LogTableWidget::verticalScrollbarValueChanged(int value)
{
(void)value;
frame()->updateAncestorCommitMap();
}
diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
index a515fbb..0ae6460 100644
--- a/src/MainWindow.cpp
+++ b/src/MainWindow.cpp
@@ -1,3425 +1,3472 @@
#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(HunkIndexRole, -1);
+ item->setData(ObjectIdRole, data.id);
+// item->setData(HunkIndexRole, -1);
item->setData(HeaderRole, header);
- item->setData(IsSubmoduleRole, issubmodule);
+ 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(uncommited ? QString() : id, &ok);
+ 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(id, &ok);
+ 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())[key] = diff;
+ (*getDiffCacheMap(frame))[key] = diff;
}
}
/**
* @brief ファイルリストを更新
* @param id
* @param diff_list
* @param listwidget
*/
-void MainWindow::updateFilesList2(QString const &id, QList<Git::Diff> *diff_list, QListWidget *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());
+ 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();
+ 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()->setup(g);
+ 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)) {
+ 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(g);
+ queryBranches(frame, g);
// タグを取得
- ptrTagMap()->clear();
+ ptrTagMap(frame)->clear();
QList<Git::Tag> tags = g->tags();
for (Git::Tag const &tag : tags) {
Git::Tag t = tag;
- t.id = getObjCache()->getCommitIdFromTag(t.id);
- (*ptrTagMap())[t.id].push_back(t);
+ 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()->revParse("HEAD"));
+ 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())[row]);
+ 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)
{
if (commit.isEmpty()) return;
GitPtr g = git();
if (!isValidWorkingCopy(g)) return;
g->mergeBranch(commit, ff);
openRepository(true);
}
void MainWindow::mergeBranch(Git::CommitItem const *commit, Git::MergeFastForward ff)
{
if (!commit) return;
mergeBranch(commit->commit_id, ff);
}
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(row);
+ 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();
{
MySettings s;
s.beginGroup("Behavior");
s.setValue(MergeFastForward, fastforward);
s.endGroup();
}
QString from = dlg.mergeFrom();
mergeBranch(from, MergeDialog::ff(fastforward));
}
}
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(row);
+ 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(row);
+ 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(commit->commit_id, nullptr).isEmpty() ? nullptr : menu.addAction(tr("Delete remote branch..."));
+ 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(this, commit);
+ checkout(frame(), this, commit);
return;
}
if (a == a_delbranch) {
- deleteBranch(commit);
+ deleteBranch(frame(), commit);
return;
}
if (a == a_delrembranch) {
- deleteRemoteBranch(commit);
+ 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(this, commit);
+ 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) {
- execFilePropertyDialog(item);
+ 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) {
- execFilePropertyDialog(item);
+ 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) {
- execFilePropertyDialog(item);
+ 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);
}
}
}
-void MainWindow::checkout(QWidget *parent, const Git::CommitItem *commit, std::function<void ()> accepted_callback)
+/**
+ * @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(Branches | Tags | Remotes);
+ 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(this, selectedCommitItem(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()->find(key);
- if (it != getDiffCacheMap()->end()) {
+ 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)
{
- execFilePropertyDialog(item);
+ showObjectProperty(item);
}
void MainWindow::on_listWidget_staged_itemDoubleClicked(QListWidgetItem *item)
{
- execFilePropertyDialog(item);
+ showObjectProperty(item);
}
void MainWindow::on_listWidget_files_itemDoubleClicked(QListWidgetItem *item)
{
- execFilePropertyDialog(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(Branches | Tags | Remotes);
+ 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()->getCommitIdFromTag(id);
+ 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(selectedCommitItem(frame()));
+ deleteRemoteBranch(frame(), selectedCommitItem(frame()));
}
void MainWindow::on_action_terminal_triggered()
{
auto const *repo = &currentRepository();
openTerminal(repo);
}
void MainWindow::on_action_explorer_triggered()
{
auto const *repo = &currentRepository();
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->workingRepositoryDir() / mod.path, g->sshKey());
+ 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()
{
- SubmoduleMainWindow *w = new SubmoduleMainWindow(this);
- w->show();
- w->reset();
}
diff --git a/src/MainWindow.h b/src/MainWindow.h
index 94baf1f..eec30a8 100644
--- a/src/MainWindow.h
+++ b/src/MainWindow.h
@@ -1,574 +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;
+// 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 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(Git::CommitItem const *commit);
- QStringList remoteBranches(QString const &id, QStringList *all);
+ 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);
- QString getObjectID(QListWidgetItem *item);
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(const QString &id, const QString &dstpath);
+ 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(const QString &id);
+ 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(QString id, bool *ok);
- void queryBranches(const GitPtr &g);
+ 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(const Git::CommitItem *commit);
+ 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(const QString &id);
+ QList<Git::Branch> findBranch(RepositoryWrapperFrame *frame, const QString &id);
QString tempfileHeader() const;
void deleteTempFiles();
- QString getCommitIdFromTag(const QString &tag);
+ QString getCommitIdFromTag(RepositoryWrapperFrame *frame, const QString &tag);
QString newTempFilePath();
int limitLogCount() const;
- Git::Object cat_file_(const GitPtr &g, const QString &id);
+ 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();
+ 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(int row) const;
+ 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(int flags);
+ 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 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(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();
+ 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();
- const std::map<int, QList<BranchLabel> > *getLabelMap() const;
- void clearLabelMap();
- GitObjectCache *getObjCache();
+ 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();
+ 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(const QString &id, QList<Git::Diff> *diff_list, QListWidget *listwidget);
+ 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(QWidget *parent, const Git::CommitItem *commit);
+ void execCommitExploreWindow(RepositoryWrapperFrame *frame, QWidget *parent, const Git::CommitItem *commit);
void execFileHistory(const QString &path);
void execFileHistory(QListWidgetItem *item);
- void execFilePropertyDialog(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 &currentBranch() const;
void setCurrentBranch(const Git::Branch &b);
const RepositoryItem &currentRepository() const;
QString currentRepositoryName() const;
QString currentRemoteName() const;
QString currentBranchName() const;
- GitPtr git(const QString &dir, const QString &sshkey = {}) const;
+ GitPtr git(const QString &dir, const QString &submodpath, const QString &sshkey) const;
GitPtr git();
- GitPtr git(const Git::SubmoduleItem &submod);
+ GitPtr git(Git::SubmoduleItem const &submodpath);
void autoOpenRepository(QString dir);
bool queryCommit(const QString &id, Git::CommitItem *out);
- void checkout(QWidget *parent, const Git::CommitItem *commit, std::function<void ()> accepted_callback = {});
+ 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(const QString &id);
+ Git::Object cat_file(RepositoryWrapperFrame *frame, const QString &id);
void addWorkingCopyDir(const QString &dir, bool open);
- bool saveAs(const QString &id, const QString &dstpath);
+ 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(const QString &commit_id, const QString &file);
+ 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(int row) 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/MyImageViewWidget.cpp b/src/MyImageViewWidget.cpp
index 8090ef3..cf28be2 100644
--- a/src/MyImageViewWidget.cpp
+++ b/src/MyImageViewWidget.cpp
@@ -1,52 +1,52 @@
#include "MyImageViewWidget.h"
#include <QMenu>
#include <QFileDialog>
#include "common/joinpath.h"
#include "common/misc.h"
MyImageViewWidget::MyImageViewWidget(QWidget *parent)
: ImageViewWidget(parent)
{
}
void MyImageViewWidget::setImage(QString const &mimetype, QByteArray const &ba, QString const &object_id, QString const &path)
{
this->object_id_ = object_id;
this->path_ = path;
ImageViewWidget::setImage(mimetype, ba);
}
void MyImageViewWidget::contextMenuEvent(QContextMenuEvent *e)
{
QString id = object_id_;
if (id.startsWith(PATH_PREFIX)) {
// pass
} else if (Git::isValidID(id)) {
// pass
} else {
return; // invalid id
}
QMenu menu;
QAction *a_save_as = id.isEmpty() ? nullptr : menu.addAction(tr("Save as..."));
if (!menu.actions().isEmpty()) {
update();
QAction *a = menu.exec(misc::contextMenuPos(this, e));
if (a) {
if (a == a_save_as) {
auto *mw = qobject_cast<MainWindow *>(mainwindow());
QString path = mw->currentWorkingCopyDir() / path_;
QString dstpath = QFileDialog::getSaveFileName(window(), tr("Save as"), path);
if (!dstpath.isEmpty()) {
- mw->saveAs(id, dstpath);
+ mw->saveAs(mw->frame(), id, dstpath);
}
update();
return;
}
}
}
}
diff --git a/src/MyTextEditorWidget.cpp b/src/MyTextEditorWidget.cpp
index 970fd0f..7a9fe0e 100644
--- a/src/MyTextEditorWidget.cpp
+++ b/src/MyTextEditorWidget.cpp
@@ -1,57 +1,57 @@
#include "MyTextEditorWidget.h"
#include "MainWindow.h"
#include "common/misc.h"
#include <QMenu>
#include <QFileDialog>
#include "common/joinpath.h"
MyTextEditorWidget::MyTextEditorWidget(QWidget *parent)
: TextEditorWidget(parent)
{
}
void MyTextEditorWidget::setDocument(const QList<Document::Line> *source, MainWindow *mw, QString const &object_id, QString const &object_path)
{
this->mainwindow = mw;
this->object_id = object_id;
this->object_path = object_path;
TextEditorWidget::setDocument(source);
}
void MyTextEditorWidget::contextMenuEvent(QContextMenuEvent *event)
{
QString id = object_id;
if (id.startsWith(PATH_PREFIX)) {
// pass
} else if (Git::isValidID(id)) {
// pass
} else {
return; // invalid id
}
QMenu menu;
QAction *a_save_as = id.isEmpty() ? nullptr : menu.addAction(tr("Save as..."));
QAction *a_copy = menu.addAction(tr("Copy"));
if (!menu.actions().isEmpty()) {
update();
QAction *a = menu.exec(misc::contextMenuPos(this, event));
if (a) {
if (a == a_save_as) {
QString path = mainwindow->currentWorkingCopyDir() / object_path;
QString dstpath = QFileDialog::getSaveFileName(window(), tr("Save as"), path);
if (!dstpath.isEmpty()) {
- mainwindow->saveAs(id, dstpath);
+ mainwindow->saveAs(mainwindow->frame(), id, dstpath);
}
update();
return;
}
if (a == a_copy) {
editCopy();
return;
}
}
}
}
diff --git a/src/ReflogWindow.cpp b/src/ReflogWindow.cpp
index 494ad5c..9905296 100644
--- a/src/ReflogWindow.cpp
+++ b/src/ReflogWindow.cpp
@@ -1,126 +1,126 @@
#include "CommitExploreWindow.h"
#include "ReflogWindow.h"
#include "ui_ReflogWindow.h"
#include "MainWindow.h"
#include "Git.h"
#include <QMenu>
ReflogWindow::ReflogWindow(QWidget *parent, MainWindow *mainwin, Git::ReflogItemList const &reflog)
: QDialog(parent)
, ui(new Ui::ReflogWindow)
{
ui->setupUi(this);
Qt::WindowFlags flags = windowFlags();
flags &= ~Qt::WindowContextHelpButtonHint;
flags |= Qt::WindowMaximizeButtonHint;
setWindowFlags(flags);
mainwindow_ = mainwin;
reflog_ = reflog;
updateTable(reflog_);
}
ReflogWindow::~ReflogWindow()
{
delete ui;
}
void ReflogWindow::updateTable(Git::ReflogItemList const &reflog)
{
QTableWidgetItem *item;
ui->tableWidget->clear();
QStringList cols = {
tr("Commit"),
tr("Head"),
tr("Command"),
tr("Message"),
};
auto newQTableWidgetItem = [](QString const &text){
auto *item = new QTableWidgetItem(text);
return item;
};
ui->tableWidget->setColumnCount(cols.size());
ui->tableWidget->setRowCount(reflog.size());
for (int col = 0; col < cols.size(); col++) {
item = newQTableWidgetItem(cols[col]);
ui->tableWidget->setHorizontalHeaderItem(col, item);
}
int row = 0;
for (Git::ReflogItem const &t : reflog) {
QString text = t.id.mid(0, 7);
item = newQTableWidgetItem(text);
ui->tableWidget->setItem(row, 0, item);
item = newQTableWidgetItem(t.head);
ui->tableWidget->setItem(row, 1, item);
QString cmd = t.command;
int i = cmd.indexOf(' ');
if (i < 0) i = cmd.size();
if (i > 10) i = 10;
cmd = cmd.mid(0, i);
item = newQTableWidgetItem(cmd);
ui->tableWidget->setItem(row, 2, item);
item = newQTableWidgetItem(t.message);
ui->tableWidget->setItem(row, 3, item);
ui->tableWidget->setRowHeight(row, 24);
row++;
}
ui->tableWidget->resizeColumnsToContents();
ui->tableWidget->horizontalHeader()->setStretchLastSection(true);
}
bool ReflogWindow::currentCommit(Git::CommitItem *out)
{
bool ok = false;
*out = Git::CommitItem();
int row = ui->tableWidget->currentRow();
if (row >= 0 && row < reflog_.size()) {
Git::ReflogItem const &logitem = reflog_[row];
if (mainwindow()->queryCommit(logitem.id, out)) {
ok = true;
}
}
return ok;
}
void ReflogWindow::on_tableWidget_customContextMenuRequested(const QPoint &pos)
{
Git::CommitItem commit;
if (!currentCommit(&commit)) return;
QMenu menu;
QAction *a_checkout = menu.addAction(tr("Checkout"));
QAction *a_explorer = menu.addAction(tr("Explorer"));
QAction *a_property = mainwindow()->addMenuActionProperty(&menu);
QAction *a = menu.exec(ui->tableWidget->viewport()->mapToGlobal(pos) + QPoint(8, -8));
if (a) {
if (a == a_checkout) {
- mainwindow()->checkout(this, &commit);
+ mainwindow()->checkout(mainwindow()->frame(), this, &commit);
return;
}
if (a == a_explorer) {
- mainwindow()->execCommitExploreWindow(this, &commit);
+ mainwindow()->execCommitExploreWindow(mainwindow()->frame(), this, &commit);
return;
}
if (a == a_property) {
mainwindow()->execCommitPropertyDialog(this, mainwindow()->frame(), &commit);
return;
}
}
}
void ReflogWindow::on_tableWidget_itemDoubleClicked(QTableWidgetItem *item)
{
(void)item;
Git::CommitItem commit;
if (!currentCommit(&commit)) return;
mainwindow()->execCommitPropertyDialog(this, mainwindow()->frame(), &commit);
}
diff --git a/src/RepositoryWrapperFrame.cpp b/src/RepositoryWrapperFrame.cpp
index 83f81a5..d99686a 100644
--- a/src/RepositoryWrapperFrame.cpp
+++ b/src/RepositoryWrapperFrame.cpp
@@ -1,144 +1,144 @@
#include "LogTableWidget.h"
#include "MainWindow.h"
#include "RepositoryWrapperFrame.h"
RepositoryWrapperFrame::RepositoryWrapperFrame(QWidget *parent)
: QFrame(parent)
{
}
void RepositoryWrapperFrame::bind(MainWindow *mw, LogTableWidget *logtablewidget, FilesListWidget *fileslistwidget, FilesListWidget *unstagedfileslistwidget, FilesListWidget *stagesfileslistwidget, FileDiffWidget *filediffwidget)
{
mw_ = mw;
logtablewidget_ = logtablewidget;
fileslistwidget_ = fileslistwidget;
unstagedfileslistwidget_ = unstagedfileslistwidget;
stagesfileslistwidget_ = stagesfileslistwidget;
filediffwidget_ = filediffwidget;
logtablewidget->bind(this);
}
MainWindow *RepositoryWrapperFrame::mainwindow()
{
Q_ASSERT(mw_);
return mw_;
}
MainWindow const *RepositoryWrapperFrame::mainwindow() const
{
Q_ASSERT(mw_);
return mw_;
}
LogTableWidget *RepositoryWrapperFrame::logtablewidget()
{
return logtablewidget_;
}
FilesListWidget *RepositoryWrapperFrame::fileslistwidget()
{
return fileslistwidget_;
}
FilesListWidget *RepositoryWrapperFrame::unstagedFileslistwidget()
{
return unstagedfileslistwidget_;
}
FilesListWidget *RepositoryWrapperFrame::stagedFileslistwidget()
{
return stagesfileslistwidget_;
}
FileDiffWidget *RepositoryWrapperFrame::filediffwidget()
{
return filediffwidget_;
}
const Git::CommitItem *RepositoryWrapperFrame::commitItem(int row)
{
return mainwindow()->commitItem(mainwindow()->frame(), row);
}
QIcon RepositoryWrapperFrame::verifiedIcon(char s) const
{
return mainwindow()->verifiedIcon(s);
}
QIcon RepositoryWrapperFrame::committerIcon(int row) const
{
- return mainwindow()->committerIcon(const_cast<RepositoryWrapperFrame *>(mainwindow()->frame()), row);
+ return mainwindow()->committerIcon(const_cast<RepositoryWrapperFrame *>(this), row);
}
QList<BranchLabel> const *RepositoryWrapperFrame::label(int row) const
{
- return mainwindow()->label(row);
+ return mainwindow()->label(this, row);
}
QString RepositoryWrapperFrame::currentBranchName() const
{
return mainwindow()->currentBranchName();
}
const Git::CommitItemList &RepositoryWrapperFrame::getLogs() const
{
- return mainwindow()->getLogs(mainwindow()->frame());
+ return mainwindow()->getLogs(this);
}
bool RepositoryWrapperFrame::isAncestorCommit(const QString &id)
{
return mainwindow()->isAncestorCommit(id);
}
QColor RepositoryWrapperFrame::color(unsigned int i)
{
return mainwindow()->color(i);
}
void RepositoryWrapperFrame::updateAncestorCommitMap()
{
mainwindow()->updateAncestorCommitMap(this);
}
void RepositoryWrapperFrame::updateLogTableView()
{
logtablewidget_->viewport()->update();
}
void RepositoryWrapperFrame::setFocusToLogTable()
{
logtablewidget_->setFocus();
}
void RepositoryWrapperFrame::selectLogTableRow(int row)
{
logtablewidget_->selectRow(row);
}
void RepositoryWrapperFrame::prepareLogTableWidget()
{
QStringList cols = {
tr("Graph"),
tr("Commit"),
tr("Date"),
tr("Author"),
tr("Message"),
};
int n = cols.size();
logtablewidget_->setColumnCount(n);
logtablewidget_->setRowCount(0);
for (int i = 0; i < n; i++) {
QString const &text = cols[i];
auto *item = new QTableWidgetItem(text);
logtablewidget_->setHorizontalHeaderItem(i, item);
}
mainwindow()->updateCommitGraph(this); // コミットグラフを更新
}
void RepositoryWrapperFrame::clearLogContents()
{
logtablewidget_->clearContents();
logtablewidget_->scrollToTop();
}
diff --git a/src/RepositoryWrapperFrame.h b/src/RepositoryWrapperFrame.h
index c43585e..eecf445 100644
--- a/src/RepositoryWrapperFrame.h
+++ b/src/RepositoryWrapperFrame.h
@@ -1,67 +1,76 @@
#ifndef REPOSITORYWRAPPERFRAME_H
#define REPOSITORYWRAPPERFRAME_H
#include "BranchLabel.h"
#include "Git.h"
+#include "GitObjectManager.h"
#include <QFrame>
class MainWindow;
class LogTableWidget;
class FilesListWidget;
class FileDiffWidget;
class RepositoryWrapperFrame : public QFrame {
Q_OBJECT
friend class MainWindow;
private:
Git::CommitItemList logs;
MainWindow *mw_ = nullptr;
LogTableWidget *logtablewidget_ = nullptr;
FilesListWidget *fileslistwidget_ = nullptr;
FilesListWidget *unstagedfileslistwidget_ = nullptr;
FilesListWidget *stagesfileslistwidget_ = nullptr;
FileDiffWidget *filediffwidget_ = nullptr;
+
+ 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;
+
MainWindow *mainwindow();
MainWindow const *mainwindow() const;
public:
explicit RepositoryWrapperFrame(QWidget *parent = nullptr);
Git::CommitItem const *commitItem(int row);
QIcon verifiedIcon(char s) const;
QIcon committerIcon(int row) const;
const QList<BranchLabel> *label(int row) const;
QString currentBranchName() const;
const Git::CommitItemList &getLogs() const;
bool isAncestorCommit(const QString &id);
QColor color(unsigned int i);
void updateAncestorCommitMap();
void bind(MainWindow *mw
, LogTableWidget *logtablewidget
, FilesListWidget *fileslistwidget
, FilesListWidget *unstagedfileslistwidget
, FilesListWidget *stagesfileslistwidget
, FileDiffWidget *filediffwidget
);
void prepareLogTableWidget();
void clearLogContents();
LogTableWidget *logtablewidget();
FilesListWidget *fileslistwidget();
FilesListWidget *unstagedFileslistwidget();
FileDiffWidget *filediffwidget();
FilesListWidget *stagedFileslistwidget();
void updateLogTableView();
void setFocusToLogTable();
void selectLogTableRow(int row);
};
struct RepositoryWrapperFrameP {
RepositoryWrapperFrame *pointer;
RepositoryWrapperFrameP(RepositoryWrapperFrame *pointer = nullptr)
: pointer(pointer)
{
}
};
#endif // REPOSITORYWRAPPERFRAME_H
diff --git a/src/SubmoduleAddDialog.cpp b/src/SubmoduleAddDialog.cpp
index 420515f..f5c0aad 100644
--- a/src/SubmoduleAddDialog.cpp
+++ b/src/SubmoduleAddDialog.cpp
@@ -1,138 +1,138 @@
#include "SubmoduleAddDialog.h"
#include "ui_SubmoduleAddDialog.h"
#include "ApplicationGlobal.h"
#include "MainWindow.h"
#include "SearchFromGitHubDialog.h"
#include "common/joinpath.h"
#include "common/misc.h"
#include <QFileDialog>
#include <QMessageBox>
#include <QStandardPaths>
#include <QThread>
enum SearchRepository {
None,
GitHub,
};
struct SubmoduleAddDialog::Private {
QString url;
QString repo_name;
QString default_working_dir;
bool ok = false;
QString errmsg;
};
SubmoduleAddDialog::SubmoduleAddDialog(MainWindow *parent, QString const &url, QString const &defworkdir, Git::Context const *gcx)
: QDialog(parent)
, ui(new Ui::SubmoduleAddDialog)
, m(new Private)
{
ui->setupUi(this);
Qt::WindowFlags flags = windowFlags();
flags &= ~Qt::WindowContextHelpButtonHint;
setWindowFlags(flags);
m->default_working_dir = defworkdir;
ui->lineEdit_working_dir->setText(m->default_working_dir);
ui->lineEdit_repo_location->setText(url);
ui->advanced_option->setSshKeyOverrigingEnabled(!gcx->ssh_command.isEmpty());
ui->lineEdit_repo_location->setFocus();
}
SubmoduleAddDialog::~SubmoduleAddDialog()
{
delete m;
delete ui;
}
MainWindow *SubmoduleAddDialog::mainwindow()
{
return qobject_cast<MainWindow *>(parent());
}
QString SubmoduleAddDialog::url()
{
return ui->lineEdit_repo_location->text();
}
QString SubmoduleAddDialog::dir()
{
return ui->lineEdit_working_dir->text();
}
bool SubmoduleAddDialog::isForce() const
{
return ui->checkBox_force->isChecked();
}
void SubmoduleAddDialog::on_lineEdit_repo_location_textChanged(QString const &text)
{
QString path;
int i = text.lastIndexOf('/');
int j = text.lastIndexOf('\\');
if (i < j) i = j;
j = text.size();
if (text.endsWith(".git")) {
j -= 4;
}
if (i >= 0 && i < j) {
path = text.mid(i, j - i);
}
m->repo_name = path;
path = m->default_working_dir / m->repo_name;
path = misc::normalizePathSeparator(path);
ui->lineEdit_working_dir->setText(path);
}
void SubmoduleAddDialog::on_pushButton_test_clicked()
{
mainwindow()->testRemoteRepositoryValidity(url(), overridedSshKey());
}
void SubmoduleAddDialog::on_pushButton_browse_clicked()
{
QString path = ui->lineEdit_working_dir->text();
path = QFileDialog::getExistingDirectory(this, tr("Checkout into"), path);
if (!path.isEmpty()) {
m->default_working_dir = path;
path = m->default_working_dir / m->repo_name;
path = misc::normalizePathSeparator(path);
ui->lineEdit_working_dir->setText(path);
}
}
void SubmoduleAddDialog::on_pushButton_open_existing_clicked()
{
QString dir = mainwindow()->defaultWorkingDir();
dir = QFileDialog::getExistingDirectory(this, tr("Open existing directory"), dir);
if (QFileInfo(dir).isDir()) {
QString url;
- GitPtr g = mainwindow()->git(dir);
+ GitPtr g = mainwindow()->git(dir, {}, {});
QList<Git::Remote> vec;
if (g->isValidWorkingCopy()) {
g->getRemoteURLs(&vec);
}
for (Git::Remote const &r : vec) {
if (r.purpose == "push" || url.isEmpty()) {
url = r.url;
}
}
ui->lineEdit_repo_location->setText(url);
ui->lineEdit_working_dir->setText(dir);
done(Accepted);
}
}
QString SubmoduleAddDialog::overridedSshKey() const
{
return ui->advanced_option->sshKey();
}
diff --git a/src/SubmoduleMainWindow.cpp b/src/SubmoduleMainWindow.cpp
index 37033f8..af785a5 100644
--- a/src/SubmoduleMainWindow.cpp
+++ b/src/SubmoduleMainWindow.cpp
@@ -1,40 +1,52 @@
#include "SubmoduleMainWindow.h"
#include "ui_SubmoduleMainWindow.h"
-SubmoduleMainWindow::SubmoduleMainWindow(MainWindow *parent)
+SubmoduleMainWindow::SubmoduleMainWindow(MainWindow *parent, GitPtr g)
: QMainWindow(parent)
, ui(new Ui::SubmoduleMainWindow)
{
ui->setupUi(this);
mw_ = parent;
+ g_ = g;
ui->frame_repository_wrapper2->bind(mw_
- , ui->tableWidget_log
+ , ui->tableWidget_log2
, ui->listWidget_files
, ui->listWidget_unstaged
, ui->listWidget_staged
, ui->widget_diff_view
);
ui->frame_repository_wrapper2->prepareLogTableWidget();
+
+ QString text = g->workingDir();
+ text = "Submodule " + text;
+ setWindowTitle(text);
+
+ ui->stackedWidget_filelist->setCurrentWidget(ui->page_files);
}
SubmoduleMainWindow::~SubmoduleMainWindow()
{
delete ui;
}
MainWindow *SubmoduleMainWindow::mainwindow()
{
return mw_;
}
+GitPtr SubmoduleMainWindow::git()
+{
+ return g_->dup();
+}
+
RepositoryWrapperFrame *SubmoduleMainWindow::frame()
{
return ui->frame_repository_wrapper2;
}
void SubmoduleMainWindow::reset()
{
- GitPtr g = mainwindow()->git();
+ GitPtr g = git();
mainwindow()->openRepository_(ui->frame_repository_wrapper2, g);
}
diff --git a/src/SubmoduleMainWindow.h b/src/SubmoduleMainWindow.h
index 09be1d5..215fc50 100644
--- a/src/SubmoduleMainWindow.h
+++ b/src/SubmoduleMainWindow.h
@@ -1,28 +1,31 @@
#ifndef SUBMODULEMAINWINDOW_H
#define SUBMODULEMAINWINDOW_H
#include <QMainWindow>
+#include "Git.h"
class MainWindow;
class RepositoryWrapperFrame;
namespace Ui {
class SubmoduleMainWindow;
}
class SubmoduleMainWindow : public QMainWindow {
Q_OBJECT
private:
Ui::SubmoduleMainWindow *ui;
MainWindow *mw_;
+ GitPtr g_;
MainWindow *mainwindow();
+ GitPtr git();
public:
- explicit SubmoduleMainWindow(MainWindow *parent = nullptr);
+ explicit SubmoduleMainWindow(MainWindow *parent, GitPtr g);
~SubmoduleMainWindow();
RepositoryWrapperFrame *frame();
void reset();
};
#endif // SUBMODULEMAINWINDOW_H
diff --git a/src/SubmoduleMainWindow.ui b/src/SubmoduleMainWindow.ui
index 6110cf7..6eefdd1 100644
--- a/src/SubmoduleMainWindow.ui
+++ b/src/SubmoduleMainWindow.ui
@@ -1,455 +1,455 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SubmoduleMainWindow</class>
<widget class="QMainWindow" name="SubmoduleMainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>816</width>
<height>581</height>
</rect>
</property>
<property name="windowTitle">
<string>Guitar</string>
</property>
<property name="animated">
<bool>false</bool>
</property>
<widget class="QWidget" name="centralWidget">
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="RepositoryWrapperFrame" name="frame_repository_wrapper2">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QVBoxLayout" name="verticalLayout_11">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QSplitter" name="splitter_h">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="childrenCollapsible">
<bool>false</bool>
</property>
- <widget class="LogTableWidget" name="tableWidget_log">
+ <widget class="LogTableWidget" name="tableWidget_log2">
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="tabKeyNavigation">
<bool>false</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="horizontalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
<property name="wordWrap">
<bool>false</bool>
</property>
<attribute name="horizontalHeaderHighlightSections">
<bool>false</bool>
</attribute>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
</widget>
<widget class="QStackedWidget" name="stackedWidget_filelist">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="page_uncommited">
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="1,0,1">
<property name="spacing">
<number>2</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="FilesListWidget" name="listWidget_unstaged">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
</widget>
</item>
<item>
<widget class="QFrame" name="frame_2">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QGridLayout" name="gridLayout" columnstretch="1,1">
<property name="topMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<property name="spacing">
<number>3</number>
</property>
<item row="2" column="0" colspan="2">
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QToolButton" name="toolButton_unstage">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>64</width>
<height>0</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
<property name="text">
<string>Unstage</string>
</property>
<property name="icon">
<iconset resource="resources/resources.qrc">
<normaloff>:/image/unstage.svg</normaloff>:/image/unstage.svg</iconset>
</property>
<property name="iconSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextUnderIcon</enum>
</property>
</widget>
</item>
<item row="0" column="0" colspan="2">
<widget class="QToolButton" name="toolButton_select_all">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
<property name="text">
<string>Select all</string>
</property>
<property name="icon">
<iconset resource="resources/resources.qrc">
<normaloff>:/image/selall.svg</normaloff>:/image/selall.svg</iconset>
</property>
<property name="iconSize">
<size>
<width>24</width>
<height>18</height>
</size>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextUnderIcon</enum>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QToolButton" name="toolButton_stage">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>64</width>
<height>0</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
<property name="text">
<string>Stage</string>
</property>
<property name="icon">
<iconset resource="resources/resources.qrc">
<normaloff>:/image/stage.svg</normaloff>:/image/stage.svg</iconset>
</property>
<property name="iconSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextUnderIcon</enum>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QToolButton" name="toolButton_commit">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
<property name="text">
<string>Commit</string>
</property>
<property name="icon">
<iconset resource="resources/resources.qrc">
<normaloff>:/image/commit.svg</normaloff>:/image/commit.svg</iconset>
</property>
<property name="iconSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextUnderIcon</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<widget class="FilesListWidget" name="listWidget_staged">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_files">
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="FilesListWidget" name="listWidget_files">
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<widget class="QFrame" name="frame_diff_view">
<property name="frameShape">
<enum>QFrame::Panel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_6">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="FileDiffWidget" name="widget_diff_view" native="true">
<property name="minimumSize">
<size>
<width>0</width>
<height>100</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::ClickFocus</enum>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QStatusBar" name="statusBar"/>
<widget class="QMenuBar" name="menuBar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>816</width>
<height>25</height>
</rect>
</property>
</widget>
</widget>
<layoutdefault spacing="6" margin="11"/>
<customwidgets>
<customwidget>
<class>FileDiffWidget</class>
<extends>QWidget</extends>
<header>FileDiffWidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>LogTableWidget</class>
<extends>QTableWidget</extends>
<header>LogTableWidget.h</header>
</customwidget>
<customwidget>
<class>FilesListWidget</class>
<extends>QListWidget</extends>
<header>FilesListWidget.h</header>
</customwidget>
<customwidget>
<class>RepositoryWrapperFrame</class>
<extends>QFrame</extends>
<header>RepositoryWrapperFrame.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<tabstops>
- <tabstop>tableWidget_log</tabstop>
+ <tabstop>tableWidget_log2</tabstop>
<tabstop>listWidget_unstaged</tabstop>
<tabstop>toolButton_select_all</tabstop>
<tabstop>toolButton_stage</tabstop>
<tabstop>toolButton_unstage</tabstop>
<tabstop>toolButton_commit</tabstop>
<tabstop>listWidget_staged</tabstop>
<tabstop>listWidget_files</tabstop>
<tabstop>widget_diff_view</tabstop>
</tabstops>
<resources>
<include location="resources/resources.qrc"/>
</resources>
<connections/>
</ui>
diff --git a/src/WelcomeWizardDialog.cpp b/src/WelcomeWizardDialog.cpp
index d5b63a1..6d61a53 100644
--- a/src/WelcomeWizardDialog.cpp
+++ b/src/WelcomeWizardDialog.cpp
@@ -1,216 +1,216 @@
#include "WelcomeWizardDialog.h"
#include "ui_WelcomeWizardDialog.h"
#include <QFileDialog>
#include "MainWindow.h"
#include "common/misc.h"
#include "Git.h"
WelcomeWizardDialog::WelcomeWizardDialog(MainWindow *parent)
: QDialog(parent)
, ui(new Ui::WelcomeWizardDialog)
{
ui->setupUi(this);
Qt::WindowFlags flags = windowFlags();
flags &= ~Qt::WindowContextHelpButtonHint;
setWindowFlags(flags);
mainwindow_ = parent;
pages_.push_back(ui->page_helper_tools);
pages_.push_back(ui->page_global_user_information);
pages_.push_back(ui->page_default_working_folder);
pages_.push_back(ui->page_finish);
ui->stackedWidget->setCurrentWidget(pages_[0]);
on_stackedWidget_currentChanged(0);
avatar_loader_.start(mainwindow_);
connect(&avatar_loader_, &AvatarLoader::updated, [&](RepositoryWrapperFrameP frame){
QString email = ui->lineEdit_user_email->text();
QIcon icon = avatar_loader_.fetch(frame.pointer, email.toStdString(), false);
setAvatar(icon);
});
ui->stackedWidget->setCurrentWidget(ui->page_helper_tools);
}
WelcomeWizardDialog::~WelcomeWizardDialog()
{
avatar_loader_.stop();
delete ui;
}
void WelcomeWizardDialog::set_user_name(QString const &v)
{
ui->lineEdit_user_name->setText(v);
}
void WelcomeWizardDialog::set_user_email(QString const &v)
{
ui->lineEdit_user_email->setText(v);
}
void WelcomeWizardDialog::set_default_working_folder(QString const &v)
{
ui->lineEdit_default_working_folder->setText(v);
}
void WelcomeWizardDialog::set_git_command_path(QString const &v)
{
ui->lineEdit_git->setText(v);
}
void WelcomeWizardDialog::set_file_command_path(QString const &v)
{
ui->lineEdit_file->setText(v);
}
QString WelcomeWizardDialog::user_name() const
{
return ui->lineEdit_user_name->text();
}
QString WelcomeWizardDialog::user_email() const
{
return ui->lineEdit_user_email->text();
}
QString WelcomeWizardDialog::default_working_folder() const
{
return ui->lineEdit_default_working_folder->text();
}
QString WelcomeWizardDialog::git_command_path() const
{
return ui->lineEdit_git->text();
}
QString WelcomeWizardDialog::file_command_path() const
{
return ui->lineEdit_file->text();
}
void WelcomeWizardDialog::on_pushButton_prev_clicked()
{
int i = pages_.indexOf(ui->stackedWidget->currentWidget());
if (i == 0) {
done(QDialog::Rejected);
return;
}
if (i > 0) {
i--;
QWidget *w = pages_[i];
ui->stackedWidget->setCurrentWidget(w);
}
}
void WelcomeWizardDialog::on_pushButton_next_clicked()
{
if (ui->stackedWidget->currentWidget() == ui->page_finish) {
done(QDialog::Accepted);
}
int i = pages_.indexOf(ui->stackedWidget->currentWidget());
if (i + 1 < pages_.size()) {
i++;
QWidget *w = pages_[i];
ui->stackedWidget->setCurrentWidget(w);
}
}
void WelcomeWizardDialog::on_stackedWidget_currentChanged(int /*arg1*/)
{
QString prev_text;
QString next_text;
QWidget *w = ui->stackedWidget->currentWidget();
if (w == ui->page_helper_tools) {
prev_text = tr("Cancel");
ui->lineEdit_git->setFocus();
} else if (w == ui->page_global_user_information) {
if (user_name().isEmpty() && user_email().isEmpty()) {
Git::Context gcx;
gcx.git_command = git_command_path();
- Git g(gcx, {}, {});
+ Git g(gcx, {}, {}, {});
Git::User user = g.getUser(Git::Source::Global);
set_user_name(user.name);
set_user_email(user.email);
}
if (user_name().isEmpty()) {
ui->lineEdit_user_name->setFocus();
} else if (user_email().isEmpty()) {
ui->lineEdit_user_email->setFocus();
} else {
ui->pushButton_next->setFocus();
}
} else if (w == ui->page_default_working_folder) {
ui->lineEdit_default_working_folder->setFocus();
} else if (w == ui->page_finish) {
ui->lineEdit_preview_user->setText(ui->lineEdit_user_name->text());
ui->lineEdit_preview_email->setText(ui->lineEdit_user_email->text());
ui->lineEdit_preview_folder->setText(ui->lineEdit_default_working_folder->text());
ui->lineEdit_preview_git->setText(ui->lineEdit_git->text());
ui->lineEdit_preview_file->setText(ui->lineEdit_file->text());
next_text = tr("Finish");
ui->pushButton_next->setFocus();
}
ui->pushButton_prev->setText(prev_text.isEmpty() ? tr("<< Prev") : prev_text);
ui->pushButton_next->setText(next_text.isEmpty() ? tr("Next >>") : next_text);
ui->pushButton_next->setDefault(true);
}
void WelcomeWizardDialog::on_pushButton_browse_default_workiing_folder_clicked()
{
QString s = ui->lineEdit_default_working_folder->text();
s = QFileDialog::getExistingDirectory(this, tr("Default Working Folder"), s);
s = misc::normalizePathSeparator(s);
ui->lineEdit_default_working_folder->setText(s);
}
void WelcomeWizardDialog::on_pushButton_browse_git_clicked()
{
QString s = mainwindow_->selectGitCommand(false);
ui->lineEdit_git->setText(s);
}
void WelcomeWizardDialog::on_pushButton_browse_file_clicked()
{
QString s = mainwindow_->selectFileCommand(false);
ui->lineEdit_file->setText(s);
}
void WelcomeWizardDialog::setAvatar(QIcon const &icon)
{
QPixmap pm = icon.pixmap(QSize(64, 64));
ui->label_avatar->setPixmap(pm);
}
void WelcomeWizardDialog::on_pushButton_get_icon_clicked()
{
ui->label_avatar->setPixmap(QPixmap());
QString email = ui->lineEdit_user_email->text();
if (email.indexOf('@') > 0) {
QIcon icon = avatar_loader_.fetch(nullptr, email.toStdString(), true);
if (!icon.isNull()) {
setAvatar(icon);
}
}
}
void WelcomeWizardDialog::on_lineEdit_git_textChanged(const QString &arg1)
{
QString ss;
if (!misc::isExecutable(arg1)) {
ss = "* { background-color: #ffc0c0; }";
}
ui->lineEdit_git->setStyleSheet(ss);
}
void WelcomeWizardDialog::on_lineEdit_file_textChanged(const QString &arg1)
{
QString ss;
if (!misc::isExecutable(arg1)) {
ss = "* { background-color: #ffc0c0; }";
}
ui->lineEdit_file->setStyleSheet(ss);
}

File Metadata

Mime Type
text/x-diff
Expires
Sat, Feb 7, 8:52 AM (4 h, 41 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
55933
Default Alt Text
(368 KB)

Event Timeline