Page Menu
Home
Phabricator (Chris)
Search
Configure Global Search
Log In
Files
F628420
MainWindow.cpp
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Authored By
Unknown
Size
62 KB
Referenced Files
None
Subscribers
None
MainWindow.cpp
View Options
#include
"MainWindow.h"
#include
"ui_MainWindow.h"
#include
"AboutDialog.h"
#include
"CheckoutBranchDialog.h"
#include
"CloneDialog.h"
#include
"CommitPropertyDialog.h"
#include
"ConfigCredentialHelperDialog.h"
#include
"FileUtil.h"
#include
"Git.h"
#include
"GitDiff.h"
#include
"joinpath.h"
#include
"LibGit2.h"
#include
"main.h"
#include
"MergeBranchDialog.h"
#include
"misc.h"
#include
"MySettings.h"
#include
"NewBranchDialog.h"
#include
"Terminal.h"
#include
"PushDialog.h"
#include
"RepositoryData.h"
#include
"SelectCommandDialog.h"
#include
"SettingsDialog.h"
#include
"TextEditDialog.h"
#include
"RepositoryPropertyDialog.h"
#include
"EditTagDialog.h"
#include
"DeleteTagsDialog.h"
#include
"FileHistoryWindow.h"
#include
"FilePropertyDialog.h"
#include
<deque>
#include
<QDateTime>
#include
<QDebug>
#include
<QDesktopServices>
#include
<QFileDialog>
#include
<QMessageBox>
#include
<QPainter>
#include
<QStandardPaths>
#include
<QKeyEvent>
#include
<set>
#include
<QProcess>
#include
<QDirIterator>
#include
<QThread>
#ifdef Q_OS_WIN
#include
"win32/win32.h"
#endif
FileDiffWidget
::
DrawData
::
DrawData
()
{
bgcolor_text
=
QColor
(
255
,
255
,
255
);
bgcolor_gray
=
QColor
(
224
,
224
,
224
);
bgcolor_add
=
QColor
(
192
,
240
,
192
);
bgcolor_del
=
QColor
(
255
,
224
,
224
);
bgcolor_add_dark
=
QColor
(
64
,
192
,
64
);
bgcolor_del_dark
=
QColor
(
240
,
64
,
64
);
}
enum
{
IndexRole
=
Qt
::
UserRole
,
FilePathRole
,
DiffIndexRole
,
HunkIndexRole
,
};
enum
{
GroupItem
=
-1
,
};
static
inline
bool
isGroupItem
(
QTreeWidgetItem
*
item
)
{
if
(
item
)
{
int
index
=
item
->
data
(
0
,
IndexRole
).
toInt
();
if
(
index
==
GroupItem
)
{
return
true
;
}
}
return
false
;
}
static
inline
QString
getFilePath
(
QListWidgetItem
*
item
)
{
if
(
!
item
)
return
QString
();
return
item
->
data
(
FilePathRole
).
toString
();
}
static
inline
int
indexOfLog
(
QListWidgetItem
*
item
)
{
if
(
!
item
)
return
-1
;
return
item
->
data
(
IndexRole
).
toInt
();
}
static
inline
int
indexOfDiff
(
QListWidgetItem
*
item
)
{
if
(
!
item
)
return
-1
;
return
item
->
data
(
DiffIndexRole
).
toInt
();
}
static
inline
int
getHunkIndex
(
QListWidgetItem
*
item
)
{
if
(
!
item
)
return
-1
;
return
item
->
data
(
HunkIndexRole
).
toInt
();
}
struct
MainWindow
::
Private
{
Git
::
Context
gcx
;
ApplicationSettings
appsettings
;
QString
file_command
;
QList
<
RepositoryItem
>
repos
;
RepositoryItem
current_repo
;
Git
::
Branch
current_branch
;
QString
head_id
;
struct
Diff
{
QList
<
Git
::
Diff
>
result
;
std
::
shared_ptr
<
QThread
>
thread
;
QList
<
std
::
shared_ptr
<
QThread
>>
garbage
;
}
diff
;
std
::
map
<
QString
,
Git
::
Diff
>
diff_cache
;
QStringList
added
;
Git
::
CommitItemList
logs
;
std
::
map
<
int
,
QList
<
Label
>>
label_map
;
bool
uncommited_changes
=
false
;
int
timer_interval_ms
=
0
;
int
update_files_list_counter
=
0
;
int
interval_250ms_counter
=
0
;
QImage
graph_color
;
std
::
map
<
QString
,
QList
<
Git
::
Branch
>>
branch_map
;
std
::
map
<
QString
,
QList
<
Git
::
Tag
>>
tag_map
;
QString
repository_filter_text
;
QPixmap
digits
;
QIcon
repository_icon
;
QIcon
folder_icon
;
unsigned
int
temp_file_counter
=
0
;
GitObjectCache
objcache
;
QPixmap
transparent_pixmap
;
};
MainWindow
::
MainWindow
(
QWidget
*
parent
)
:
QMainWindow
(
parent
),
ui
(
new
Ui
::
MainWindow
)
{
pv
=
new
Private
();
ui
->
setupUi
(
this
);
ui
->
splitter_v
->
setSizes
({
100
,
400
});
ui
->
splitter_h
->
setSizes
({
200
,
100
,
200
});
ui
->
widget_diff_view
->
bind
(
this
);
ui
->
treeWidget_repos
->
installEventFilter
(
this
);
ui
->
tableWidget_log
->
installEventFilter
(
this
);
ui
->
listWidget_staged
->
installEventFilter
(
this
);
ui
->
listWidget_unstaged
->
installEventFilter
(
this
);
showFileList
(
FilesListType
::
SingleList
);
pv
->
digits
.
load
(
":/image/digits.png"
);
pv
->
graph_color
.
load
(
":/image/graphcolor.png"
);
pv
->
repository_icon
=
QIcon
(
":/image/repository.png"
);
pv
->
folder_icon
=
QIcon
(
":/image/folder.png"
);
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
SettingsDialog
::
loadSettings
(
&
pv
->
appsettings
);
{
MySettings
s
;
s
.
beginGroup
(
"Global"
);
pv
->
gcx
.
git_command
=
s
.
value
(
"GitCommand"
).
toString
();
pv
->
file_command
=
s
.
value
(
"FileCommand"
).
toString
();
s
.
endGroup
();
}
#if USE_LIBGIT2
LibGit2
::
init
();
// LibGit2::test();
#endif
connect
(
ui
->
treeWidget_repos
,
SIGNAL
(
dropped
()),
this
,
SLOT
(
onRepositoriesTreeDropped
()));;
QString
path
=
getBookmarksFilePath
();
pv
->
repos
=
RepositoryBookmark
::
load
(
path
);
updateRepositoriesList
();
pv
->
timer_interval_ms
=
20
;
pv
->
update_files_list_counter
=
0
;
pv
->
interval_250ms_counter
=
0
;
startTimer
(
pv
->
timer_interval_ms
);
}
MainWindow
::~
MainWindow
()
{
#if USE_LIBGIT2
LibGit2
::
shutdown
();
#endif
cleanupDiffThread
();
deleteTempFiles
();
delete
pv
;
delete
ui
;
}
QString
MainWindow
::
getObjectID
(
QListWidgetItem
*
item
)
{
int
i
=
indexOfDiff
(
item
);
if
(
i
>=
0
&&
i
<
pv
->
diff
.
result
.
size
())
{
Git
::
Diff
const
&
diff
=
pv
->
diff
.
result
[
i
];
return
diff
.
blob
.
a_id
;
}
return
QString
();
}
bool
MainWindow
::
saveRepositoryBookmarks
()
const
{
QString
path
=
getBookmarksFilePath
();
return
RepositoryBookmark
::
save
(
path
,
&
pv
->
repos
);
}
void
MainWindow
::
saveRepositoryBookmark
(
RepositoryItem
item
)
{
if
(
item
.
local_dir
.
isEmpty
())
return
;
if
(
item
.
name
.
isEmpty
())
{
item
.
name
=
tr
(
"Unnamed"
);
}
bool
done
=
false
;
for
(
int
i
=
0
;
i
<
pv
->
repos
.
size
();
i
++
)
{
RepositoryItem
*
p
=
&
pv
->
repos
[
i
];
if
(
item
.
local_dir
==
p
->
local_dir
)
{
*
p
=
item
;
done
=
true
;
break
;
}
}
if
(
!
done
)
{
pv
->
repos
.
push_back
(
item
);
}
saveRepositoryBookmarks
();
updateRepositoriesList
();
}
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
);
}
pv
->
repos
=
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
pv
->
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
,
pv
->
digits
,
n
*
w
,
0
,
w
,
h
);
}
QString
MainWindow
::
defaultWorkingDir
()
const
{
return
pv
->
appsettings
.
default_working_dir
;
}
QColor
MainWindow
::
color
(
unsigned
int
i
)
{
unsigned
int
n
=
pv
->
graph_color
.
width
();
if
(
n
>
0
)
{
n
--
;
if
(
i
>
n
)
i
=
n
;
QRgb
const
*
p
=
(
QRgb
const
*
)
pv
->
graph_color
.
scanLine
(
0
);
return
QColor
(
qRed
(
p
[
i
]),
qGreen
(
p
[
i
]),
qBlue
(
p
[
i
]));
}
return
Qt
::
black
;
}
bool
MainWindow
::
isThereUncommitedChanges
()
const
{
return
pv
->
uncommited_changes
;
}
Git
::
CommitItemList
const
*
MainWindow
::
logs
()
const
{
return
&
pv
->
logs
;
}
QString
MainWindow
::
currentWorkingCopyDir
()
const
{
return
pv
->
current_repo
.
local_dir
;
}
GitPtr
MainWindow
::
git
(
QString
const
&
dir
)
{
return
dir
.
isEmpty
()
?
std
::
shared_ptr
<
Git
>
()
:
std
::
shared_ptr
<
Git
>
(
new
Git
(
pv
->
gcx
,
dir
));
}
GitPtr
MainWindow
::
git
()
{
return
git
(
currentWorkingCopyDir
());
}
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
();
}
int
MainWindow
::
repositoryIndex_
(
QTreeWidgetItem
*
item
)
{
if
(
item
)
{
int
i
=
item
->
data
(
0
,
IndexRole
).
toInt
();
if
(
i
>=
0
&&
i
<
pv
->
repos
.
size
())
{
return
i
;
}
}
return
-1
;
}
RepositoryItem
const
*
MainWindow
::
repositoryItem
(
QTreeWidgetItem
*
item
)
{
int
row
=
repositoryIndex_
(
item
);
if
(
row
>=
0
&&
row
<
pv
->
repos
.
size
())
{
return
&
pv
->
repos
[
row
];
}
return
nullptr
;
}
static
QTreeWidgetItem
*
newQTreeWidgetItem
()
{
QTreeWidgetItem
*
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
,
pv
->
folder_icon
);
return
item
;
}
void
MainWindow
::
updateRepositoriesList
()
{
QString
path
=
getBookmarksFilePath
();
pv
->
repos
=
RepositoryBookmark
::
load
(
path
);
QString
filter
=
pv
->
repository_filter_text
;
ui
->
treeWidget_repos
->
clear
();
std
::
map
<
QString
,
QTreeWidgetItem
*>
parentmap
;
for
(
int
i
=
0
;
i
<
pv
->
repos
.
size
();
i
++
)
{
RepositoryItem
const
&
repo
=
pv
->
repos
[
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
,
pv
->
repository_icon
);
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
->
setCurrentWidget
(
ui
->
page_1
);
break
;
case
FilesListType
::
SideBySide
:
ui
->
stackedWidget
->
setCurrentWidget
(
ui
->
page_2
);
break
;
}
}
void
MainWindow
::
clearLog
()
{
pv
->
logs
.
clear
();
pv
->
label_map
.
clear
();
pv
->
uncommited_changes
=
false
;
ui
->
tableWidget_log
->
clearContents
();
ui
->
tableWidget_log
->
scrollToTop
();
}
void
MainWindow
::
clearFileList
()
{
showFileList
(
FilesListType
::
SingleList
);
ui
->
listWidget_unstaged
->
clear
();
ui
->
listWidget_staged
->
clear
();
ui
->
listWidget_files
->
clear
();
}
void
MainWindow
::
clearDiffView
()
{
ui
->
widget_diff_view
->
clearDiffView
();
}
void
MainWindow
::
clearRepositoryInfo
()
{
pv
->
head_id
=
QString
();
pv
->
current_branch
=
Git
::
Branch
();
ui
->
label_repo_name
->
setText
(
QString
());
ui
->
label_branch_name
->
setText
(
QString
());
}
class
DiffThread
:
public
QThread
{
private
:
struct
Data
{
GitPtr
g
;
GitObjectCache
*
objcache
;
QString
id
;
bool
uncommited
;
QList
<
Git
::
Diff
>
result
;
}
d
;
public
:
DiffThread
(
GitPtr
g
,
GitObjectCache
*
objcache
,
QString
const
&
id
,
bool
uncommited
)
{
d
.
g
=
g
;
d
.
objcache
=
objcache
;
d
.
id
=
id
;
d
.
uncommited
=
uncommited
;
}
void
run
()
{
GitDiff
dm
(
d
.
g
,
d
.
objcache
);
if
(
d
.
uncommited
)
{
dm
.
diff_uncommited
(
&
d
.
result
);
}
else
{
dm
.
diff
(
d
.
id
,
&
d
.
result
);
}
}
void
interrupt
()
{
requestInterruption
();
}
QString
id
()
const
{
return
d
.
id
;
}
void
take
(
QList
<
Git
::
Diff
>
*
out
)
{
*
out
=
std
::
move
(
d
.
result
);
}
};
void
MainWindow::cleanupDiffThread
()
{
// 全てのスレッドが終了するまで待つ
if
(
pv
->
diff
.
thread
)
{
pv
->
diff
.
thread
->
requestInterruption
();
pv
->
diff
.
garbage
.
push_back
(
pv
->
diff
.
thread
);
pv
->
diff
.
thread
.
reset
();
}
for
(
auto
ptr
:
pv
->
diff
.
garbage
)
{
if
(
ptr
)
{
ptr
->
wait
();
}
}
pv
->
diff
.
garbage
.
clear
();
}
void
MainWindow::stopDiff
()
{
if
(
pv
->
diff
.
thread
)
{
pv
->
diff
.
thread
->
requestInterruption
();
// 停止要求
pv
->
diff
.
garbage
.
push_back
(
pv
->
diff
.
thread
);
// ゴミリストに投入
pv
->
diff
.
thread
.
reset
();
// ポインタをクリア
}
// 終了したスレッドをリストから除外する
QList
<
std
::
shared_ptr
<
QThread
>>
garbage
;
for
(
auto
ptr
:
pv
->
diff
.
garbage
)
{
if
(
ptr
&&
ptr
->
isRunning
())
{
garbage
.
push_back
(
ptr
);
// 実行中
}
}
pv
->
diff
.
garbage
=
std
::
move
(
garbage
);
// リストを差し替える
}
void
MainWindow::startDiff
(
GitPtr
g
,
QString
id
)
{
// diffスレッドを開始する
if
(
!
isValidWorkingCopy
(
g
))
return
;
stopDiff
();
Git
::
FileStatusList
list
=
g
->
status
();
pv
->
uncommited_changes
=
!
list
.
empty
();
if
(
id
.
isEmpty
())
{
if
(
isThereUncommitedChanges
())
{
id
=
QString
();
// uncommited changes
}
else
{
id
=
g
->
rev_parse_HEAD
();
}
}
bool
uncommited
=
(
id
.
isEmpty
()
&&
isThereUncommitedChanges
());
DiffThread
*
th
=
new
DiffThread
(
g
->
dup
(),
&
pv
->
objcache
,
id
,
uncommited
);
pv
->
diff
.
thread
=
std
::
shared_ptr
<
QThread
>
(
th
);
th
->
start
();
}
bool
MainWindow::makeDiff
(
QString
const
&
id
,
QList
<
Git
::
Diff
>
*
out
,
bool
uncommited
)
{
// diffリストを取得する
#if SINGLE_THREAD
GitPtr
g
=
git
();
if
(
isValidWorkingCopy
(
g
))
{
GitDiff
dm
(
g
,
&
pv
->
objcache
);
if
(
dm
.
diff
(
id
,
out
,
uncommited
))
{
return
true
;
}
}
#else
// multi thread
if
(
pv
->
diff
.
thread
)
{
DiffThread
*
th
=
dynamic_cast
<
DiffThread
*>
(
pv
->
diff
.
thread
.
get
());
Q_ASSERT
(
th
);
if
(
id
==
th
->
id
()
&&
!
th
->
isInterruptionRequested
())
{
// IDが一致して中断されていない
th
->
wait
();
// 終了まで待つ
th
->
take
(
out
);
// 結果を取得
return
true
;
// success
}
}
#endif
qDebug
()
<<
"failed to makeDiff"
;
return
false
;
}
void
MainWindow::updateFilesList
(
QString
id
)
{
GitPtr
g
=
git
();
if
(
!
isValidWorkingCopy
(
g
))
return
;
clearFileList
();
Git
::
FileStatusList
stats
=
g
->
status
();
pv
->
uncommited_changes
=
!
stats
.
empty
();
FilesListType
files_list_type
=
FilesListType
::
SingleList
;
auto
AddItem
=
[
&
](
QString
const
&
filename
,
QString
header
,
int
idiff
,
bool
staged
){
if
(
header
.
isEmpty
())
{
header
=
"(??\?) "
;
// damn trigraph
}
QListWidgetItem
*
item
=
new
QListWidgetItem
(
header
+
filename
);
item
->
setData
(
FilePathRole
,
filename
);
item
->
setData
(
DiffIndexRole
,
idiff
);
item
->
setData
(
HunkIndexRole
,
-1
);
switch
(
files_list_type
)
{
case
FilesListType
::
SingleList
:
ui
->
listWidget_files
->
addItem
(
item
);
break
;
case
FilesListType
::
SideBySide
:
if
(
staged
)
{
ui
->
listWidget_staged
->
addItem
(
item
);
}
else
{
ui
->
listWidget_unstaged
->
addItem
(
item
);
}
break
;
}
};
if
(
id
.
isEmpty
())
{
bool
uncommited
=
isThereUncommitedChanges
();
if
(
uncommited
)
{
files_list_type
=
FilesListType
::
SideBySide
;
}
if
(
!
makeDiff
(
id
,
&
pv
->
diff
.
result
,
uncommited
))
{
return
;
}
std
::
map
<
QString
,
int
>
diffmap
;
for
(
int
idiff
=
0
;
idiff
<
pv
->
diff
.
result
.
size
();
idiff
++
)
{
Git
::
Diff
const
&
diff
=
pv
->
diff
.
result
[
idiff
];
QString
filename
=
diff
.
path
;
if
(
!
filename
.
isEmpty
())
{
diffmap
[
filename
]
=
idiff
;
}
}
showFileList
(
files_list_type
);
for
(
Git
::
FileStatus
const
&
s
:
stats
)
{
bool
staged
=
(
s
.
isStaged
()
&&
s
.
code_y
()
==
' '
);
int
idiff
=
-1
;
QString
header
;
auto
it
=
diffmap
.
find
(
s
.
path1
());
if
(
it
!=
diffmap
.
end
())
{
idiff
=
it
->
second
;
}
if
(
s
.
code
()
==
Git
::
FileStatusCode
::
Unknown
)
{
qDebug
()
<<
"something wrong..."
;
}
else
if
(
s
.
code
()
==
Git
::
FileStatusCode
::
Untracked
)
{
// nop
}
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) "
;
}
else
if
(
s
.
code_x
()
==
'M'
||
s
.
code_y
()
==
'M'
)
{
header
=
"(chg) "
;
}
AddItem
(
s
.
path1
(),
header
,
idiff
,
staged
);
}
}
else
{
if
(
!
makeDiff
(
id
,
&
pv
->
diff
.
result
,
false
))
{
return
;
}
showFileList
(
files_list_type
);
for
(
int
idiff
=
0
;
idiff
<
pv
->
diff
.
result
.
size
();
idiff
++
)
{
Git
::
Diff
const
&
diff
=
pv
->
diff
.
result
[
idiff
];
QString
header
;
switch
(
diff
.
type
)
{
case
Git
::
Diff
::
Type
::
Added
:
header
=
"(add) "
;
break
;
case
Git
::
Diff
::
Type
::
Deleted
:
header
=
"(del) "
;
break
;
case
Git
::
Diff
::
Type
::
Changed
:
header
=
"(chg) "
;
break
;
case
Git
::
Diff
::
Type
::
Renamed
:
header
=
"(ren) "
;
break
;
}
AddItem
(
diff
.
path
,
header
,
idiff
,
false
);
}
}
for
(
Git
::
Diff
const
&
diff
:
pv
->
diff
.
result
)
{
QString
key
=
GitDiff
::
makeKey
(
diff
);
pv
->
diff_cache
[
key
]
=
diff
;
}
}
void
MainWindow::updateCurrentFilesList
()
{
QTableWidgetItem
*
item
=
ui
->
tableWidget_log
->
item
(
selectedLogIndex
(),
0
);
if
(
!
item
)
return
;
int
row
=
item
->
data
(
IndexRole
).
toInt
();
int
logs
=
(
int
)
pv
->
logs
.
size
();
if
(
row
<
logs
)
{
if
(
Git
::
isUncommited
(
pv
->
logs
[
row
]))
{
updateFilesList
(
QString
());
}
else
{
QString
id
=
pv
->
logs
[
row
].
commit_id
;
updateFilesList
(
id
);
}
}
}
void
MainWindow::updateHeadFilesList
(
bool
wait
)
{
GitPtr
g
=
git
();
if
(
!
isValidWorkingCopy
(
g
))
return
;
startDiff
(
g
,
QString
());
// diffスレッドを開始
if
(
wait
)
updateCurrentFilesList
();
// スレッド終了を待ってリストを更新
}
void
MainWindow::prepareLogTableWidget
()
{
QStringList
cols
=
{
tr
(
"Graph"
),
tr
(
"Commit"
),
tr
(
"Date"
),
tr
(
"Author"
),
tr
(
"Description"
),
};
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
];
QTableWidgetItem
*
item
=
new
QTableWidgetItem
(
text
);
ui
->
tableWidget_log
->
setHorizontalHeaderItem
(
i
,
item
);
}
updateCommitGraph
();
// コミットグラフを更新
}
QString
MainWindow::currentRepositoryName
()
const
{
return
pv
->
current_repo
.
name
;
}
Git
::
Branch
MainWindow::currentBranch
()
const
{
return
pv
->
current_branch
;
}
bool
MainWindow::isValidWorkingCopy
(
GitPtr
const
&
g
)
const
{
return
g
&&
g
->
isValidWorkingCopy
();
}
int
MainWindow::limitLogCount
()
const
{
return
10000
;
}
QDateTime
MainWindow::limitLogTime
()
const
{
QDateTime
t
=
QDateTime
::
currentDateTime
();
t
=
t
.
addYears
(
-3
);
return
t
;
}
void
MainWindow::queryBranches
(
GitPtr
g
)
{
Q_ASSERT
(
g
);
pv
->
branch_map
.
clear
();
QList
<
Git
::
Branch
>
branches
=
g
->
branches
();
for
(
Git
::
Branch
const
&
b
:
branches
)
{
if
(
b
.
flags
&
Git
::
Branch
::
Current
)
{
pv
->
current_branch
=
b
;
}
pv
->
branch_map
[
b
.
id
].
append
(
b
);
}
}
void
MainWindow::queryTags
(
GitPtr
g
)
{
Q_ASSERT
(
g
);
pv
->
tag_map
.
clear
();
QStringList
tags
=
g
->
tags
();
for
(
QString
const
&
name
:
tags
)
{
Git
::
Tag
t
;
t
.
name
=
name
;
t
.
id
=
getCommitIdFromTag
(
name
);
pv
->
tag_map
[
t
.
id
].
push_back
(
t
);
}
}
QString
MainWindow::abbrevCommitID
(
Git
::
CommitItem
const
&
commit
)
{
return
commit
.
commit_id
.
mid
(
0
,
7
);
}
QString
MainWindow::findFileID
(
GitPtr
g
,
const
QString
&
commit_id
,
const
QString
&
file
)
{
return
lookupFileID
(
&
pv
->
objcache
,
commit_id
,
file
);
}
QList
<
MainWindow
::
Label
>
const
*
MainWindow
::
label
(
int
row
)
{
auto
it
=
pv
->
label_map
.
find
(
row
);
if
(
it
!=
pv
->
label_map
.
end
())
{
return
&
it
->
second
;
}
return
nullptr
;
}
void
MainWindow
::
openRepository_
(
GitPtr
g
)
{
clearLog
();
clearRepositoryInfo
();
pv
->
objcache
.
setup
(
g
);
if
(
isValidWorkingCopy
(
g
))
{
startDiff
(
g
,
QString
());
updateFilesList
(
QString
());
pv
->
head_id
=
g
->
rev_parse_HEAD
();
// ログを取得
pv
->
logs
=
g
->
log
(
limitLogCount
(),
limitLogTime
());
// ブランチとタグを取得
queryBranches
(
g
);
queryTags
(
g
);
QString
branch_name
=
currentBranch
().
name
;
if
(
currentBranch
().
flags
&
Git
::
Branch
::
HeadDetached
)
{
branch_name
+=
" (HEAD detached)"
;
}
ui
->
label_repo_name
->
setText
(
currentRepositoryName
());
ui
->
label_branch_name
->
setText
(
branch_name
);
}
else
{
QString
name
=
currentRepositoryName
();
ui
->
label_repo_name
->
setText
(
name
.
isEmpty
()
?
tr
(
"Unknown"
)
:
name
);
ui
->
label_branch_name
->
setText
(
"NOT FOUND"
);
}
if
(
isThereUncommitedChanges
())
{
Git
::
CommitItem
item
;
item
.
parent_ids
.
push_back
(
pv
->
current_branch
.
id
);
item
.
message
=
tr
(
"Uncommited changes"
);
pv
->
logs
.
insert
(
pv
->
logs
.
begin
(),
item
);
}
prepareLogTableWidget
();
int
index
=
0
;
int
count
=
pv
->
logs
.
size
();
ui
->
tableWidget_log
->
setRowCount
(
count
);
for
(
int
row
=
0
;
row
<
count
;
row
++
)
{
Git
::
CommitItem
const
*
commit
=
&
pv
->
logs
[
index
];
{
QTableWidgetItem
*
item
=
new
QTableWidgetItem
();
item
->
setData
(
IndexRole
,
index
);
ui
->
tableWidget_log
->
setItem
(
row
,
0
,
item
);
}
int
col
=
1
;
// カラム0はコミットグラフなので、その次から
auto
AddColumn
=
[
&
](
QString
const
&
text
,
bool
bold
,
QString
const
&
tooltip
){
QTableWidgetItem
*
item
=
new
QTableWidgetItem
(
text
);
item
->
setToolTip
(
tooltip
);
if
(
bold
)
{
QFont
font
=
item
->
font
();
font
.
setBold
(
true
);
item
->
setFont
(
font
);
}
ui
->
tableWidget_log
->
setItem
(
row
,
col
,
item
);
col
++
;
};
QString
commit_id
;
QString
datetime
;
QString
author
;
QString
message
;
QString
message_ex
;
bool
ishead
=
commit
->
commit_id
==
pv
->
head_id
;
bool
bold
=
false
;
{
if
(
Git
::
isUncommited
(
*
commit
))
{
// 未コミットの時
bold
=
true
;
// 太字
}
else
{
if
(
ishead
&&
!
isThereUncommitedChanges
())
{
// HEADで、未コミットがないとき
bold
=
true
;
// 太字
}
commit_id
=
abbrevCommitID
(
*
commit
);
}
datetime
=
misc
::
makeDateTimeString
(
commit
->
commit_date
);
author
=
commit
->
author
;
message
=
commit
->
message
;
{
// branch
QList
<
Git
::
Branch
>
list
=
findBranch
(
commit
->
commit_id
);
for
(
Git
::
Branch
const
&
b
:
list
)
{
Label
label
(
Label
::
Branch
);
label
.
text
=
b
.
name
;
//misc::abbrevBranchName(b.name);
if
(
b
.
ahead
>
0
)
{
label
.
text
+=
tr
(
", %1 ahead"
).
arg
(
b
.
ahead
);
}
if
(
b
.
behind
>
0
)
{
label
.
text
+=
tr
(
", %1 behind"
).
arg
(
b
.
behind
);
}
message_ex
+=
" {"
+
label
.
text
+
'}'
;
pv
->
label_map
[
row
].
push_back
(
label
);
}
}
{
// tag
QList
<
Git
::
Tag
>
list
=
findTag
(
commit
->
commit_id
);
for
(
Git
::
Tag
const
&
t
:
list
)
{
Label
label
(
Label
::
Tag
);
label
.
text
=
t
.
name
;
message_ex
+=
QString
(
" {t:%1}"
).
arg
(
label
.
text
);
pv
->
label_map
[
row
].
push_back
(
label
);
}
}
}
AddColumn
(
commit_id
,
false
,
QString
());
AddColumn
(
datetime
,
false
,
QString
());
AddColumn
(
author
,
false
,
QString
());
AddColumn
(
message
,
bold
,
message
+
message_ex
);
ui
->
tableWidget_log
->
setRowHeight
(
row
,
24
);
index
++
;
}
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
);
udpateButton
();
}
void
MainWindow
::
openRepository
(
bool
waitcursor
)
{
if
(
waitcursor
)
{
OverrideWaitCursor
;
openRepository
(
false
);
return
;
}
GitPtr
g
=
git
();
// ポインタの有効性チェックはしない(nullptrでも続行)
openRepository_
(
g
);
}
void
MainWindow
::
reopenRepository
(
std
::
function
<
void
(
GitPtr
g
)
>
callback
)
{
GitPtr
g
=
git
();
if
(
!
isValidWorkingCopy
(
g
))
return
;
OverrideWaitCursor
;
callback
(
g
);
openRepository_
(
g
);
}
void
MainWindow
::
udpateButton
()
{
Git
::
Branch
b
=
currentBranch
();
int
n
;
n
=
b
.
ahead
>
0
?
b
.
ahead
:
-1
;
ui
->
toolButton_push
->
setNumber
(
n
);
n
=
b
.
behind
>
0
?
b
.
behind
:
-1
;
ui
->
toolButton_pull
->
setNumber
(
n
);
}
void
MainWindow
::
autoOpenRepository
(
QString
dir
)
{
dir
=
QDir
(
dir
).
absolutePath
();
auto
Open
=
[
&
](
RepositoryItem
const
&
item
){
pv
->
current_repo
=
item
;
openRepository
();
};
for
(
RepositoryItem
const
&
item
:
pv
->
repos
)
{
Qt
::
CaseSensitivity
cs
=
Qt
::
CaseSensitive
;
#ifdef Q_OS_WIN
cs
=
Qt
::
CaseInsensitive
;
#endif
if
(
dir
.
compare
(
item
.
local_dir
,
cs
)
==
0
)
{
Open
(
item
);
return
;
}
}
RepositoryItem
newitem
;
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
;
}
}
}
void
MainWindow
::
openSelectedRepository
()
{
QTreeWidgetItem
*
ite
=
ui
->
treeWidget_repos
->
currentItem
();
RepositoryItem
const
*
item
=
repositoryItem
(
ite
);
if
(
item
)
{
pv
->
current_repo
=
*
item
;
openRepository
();
}
}
QString
MainWindow
::
getBookmarksFilePath
()
const
{
return
application_data_dir
/
"bookmarks.xml"
;
}
void
MainWindow
::
on_action_add_all_triggered
()
{
GitPtr
g
=
git
();
if
(
!
isValidWorkingCopy
(
g
))
return
;
g
->
git
(
"add --all"
);
updateHeadFilesList
(
true
);
}
void
MainWindow
::
commit
(
bool
amend
)
{
GitPtr
g
=
git
();
if
(
!
isValidWorkingCopy
(
g
))
return
;
while
(
1
)
{
TextEditDialog
dlg
;
dlg
.
setWindowTitle
(
tr
(
"Commit"
));
if
(
amend
)
{
dlg
.
setText
(
pv
->
logs
[
0
].
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
;
}
if
(
amend
)
{
g
->
commit_amend_m
(
text
);
}
else
{
g
->
commit
(
text
);
}
openRepository
();
break
;
}
else
{
break
;
}
}
}
void
MainWindow
::
commit_amend
()
{
commit
(
true
);
}
void
MainWindow
::
on_action_commit_triggered
()
{
commit
();
}
void
MainWindow
::
on_action_fetch_triggered
()
{
reopenRepository
([](
GitPtr
g
){
g
->
fetch
();
});
}
void
MainWindow
::
on_action_push_triggered
()
{
reopenRepository
([](
GitPtr
g
){
g
->
push
();
});
}
void
MainWindow
::
on_action_pull_triggered
()
{
reopenRepository
([](
GitPtr
g
){
g
->
pull
();
});
}
void
MainWindow
::
on_toolButton_push_clicked
()
{
ui
->
action_push
->
trigger
();
}
void
MainWindow
::
on_toolButton_pull_clicked
()
{
ui
->
action_pull
->
trigger
();
}
void
MainWindow
::
on_action_set_remote_origin_url_triggered
()
{
QString
newurl
;
Git
git
(
pv
->
gcx
,
currentWorkingCopyDir
());
git
.
git
(
"remote set-url origin "
+
newurl
);
}
void
MainWindow
::
on_action_config_global_credential_helper_triggered
()
{
GitPtr
g
=
git
();
if
(
!
isValidWorkingCopy
(
g
))
return
;
g
->
git
(
"config --global credential.helper"
);
QString
text
=
g
->
resultText
().
trimmed
();
ConfigCredentialHelperDialog
dlg
(
this
);
dlg
.
setHelper
(
text
);
if
(
dlg
.
exec
()
==
QDialog
::
Accepted
)
{
text
=
dlg
.
helper
();
bool
ok
=
true
;
if
(
text
.
isEmpty
())
{
text
=
"
\"\"
"
;
}
else
{
auto
isValidChar
=
[](
ushort
c
){
if
(
QChar
::
isLetterOrNumber
(
c
))
return
true
;
if
(
c
==
'_'
)
return
true
;
return
false
;
};
ushort
const
*
begin
=
text
.
utf16
();
ushort
const
*
end
=
begin
+
text
.
size
();
ushort
const
*
ptr
=
begin
;
while
(
ptr
<
end
)
{
if
(
!
isValidChar
(
*
ptr
))
{
ok
=
false
;
break
;
}
ptr
++
;
}
}
if
(
!
ok
)
{
QMessageBox
::
warning
(
this
,
tr
(
"Config credential helper"
),
tr
(
"Invalid credential helper name"
));
return
;
}
g
->
git
(
"config --global credential.helper "
+
text
);
}
}
void
MainWindow
::
on_treeWidget_repos_itemDoubleClicked
(
QTreeWidgetItem
*
/*item*/
,
int
/*column*/
)
{
openSelectedRepository
();
}
void
MainWindow
::
execCommitPropertyDialog
(
Git
::
CommitItem
const
*
commit
)
{
CommitPropertyDialog
dlg
(
this
,
*
commit
);
dlg
.
exec
();
}
QAction
*
MainWindow
::
addMenuActionProperties
(
QMenu
*
menu
)
{
return
menu
->
addAction
(
tr
(
"&Properties"
));
}
void
MainWindow
::
on_treeWidget_repos_customContextMenuRequested
(
const
QPoint
&
pos
)
{
QTreeWidgetItem
*
treeitem
=
ui
->
treeWidget_repos
->
currentItem
();
RepositoryItem
const
*
repo
=
repositoryItem
(
treeitem
);
int
index
=
treeitem
->
data
(
0
,
IndexRole
).
toInt
();
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"
));
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
;
}
}
}
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
QAction
*
a_open_folder
=
menu
.
addAction
(
tr
(
"Open &folder"
));
menu
.
addSeparator
();
QAction
*
a_remove
=
menu
.
addAction
(
tr
(
"&Remove"
));
menu
.
addSeparator
();
QAction
*
a_properties
=
addMenuActionProperties
(
&
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
)
{
QDesktopServices
::
openUrl
(
repo
->
local_dir
);
return
;
}
if
(
a
==
a_open_terminal
)
{
Terminal
::
open
(
repo
->
local_dir
);
return
;
}
if
(
a
==
a_remove
)
{
if
(
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
)
==
QMessageBox
::
Ok
)
{
pv
->
repos
.
erase
(
pv
->
repos
.
begin
()
+
index
);
saveRepositoryBookmarks
();
updateRepositoriesList
();
}
return
;
}
if
(
a
==
a_properties
)
{
RepositoryPropertyDialog
dlg
(
this
,
*
repo
);
dlg
.
exec
();
return
;
}
}
}
}
void
MainWindow
::
on_tableWidget_log_customContextMenuRequested
(
const
QPoint
&
pos
)
{
Git
::
CommitItem
const
*
commit
=
selectedCommitItem
();
if
(
commit
)
{
int
row
=
selectedLogIndex
();
QMenu
menu
;
QAction
*
a_properties
=
addMenuActionProperties
(
&
menu
);
QAction
*
a_edit_comment
=
nullptr
;
if
(
row
==
0
&&
currentBranch
().
ahead
>
0
)
{
a_edit_comment
=
menu
.
addAction
(
tr
(
"Edit comment..."
));
}
QAction
*
a_add_tag
=
menu
.
addAction
(
tr
(
"Add a tag..."
));
QAction
*
a_delete_tags
=
nullptr
;
if
(
pv
->
tag_map
.
find
(
commit
->
commit_id
)
!=
pv
->
tag_map
.
end
())
{
a_delete_tags
=
menu
.
addAction
(
tr
(
"Delete tags..."
));
}
QAction
*
a
=
menu
.
exec
(
ui
->
tableWidget_log
->
viewport
()
->
mapToGlobal
(
pos
)
+
QPoint
(
8
,
-8
));
if
(
a
)
{
if
(
a
==
a_properties
)
{
execCommitPropertyDialog
(
commit
);
return
;
}
if
(
a
==
a_edit_comment
)
{
commit_amend
();
return
;
}
if
(
a
==
a_add_tag
)
{
addTag
();
return
;
}
if
(
a
==
a_delete_tags
)
{
deleteSelectedTags
();
return
;
}
}
}
}
void
MainWindow
::
on_listWidget_files_customContextMenuRequested
(
const
QPoint
&
pos
)
{
GitPtr
g
=
git
();
if
(
!
isValidWorkingCopy
(
g
))
return
;
QMenu
menu
;
QAction
*
a_history
=
menu
.
addAction
(
tr
(
"History"
));
QAction
*
a_properties
=
addMenuActionProperties
(
&
menu
);
QPoint
pt
=
ui
->
listWidget_unstaged
->
mapToGlobal
(
pos
)
+
QPoint
(
8
,
-8
);
QAction
*
a
=
menu
.
exec
(
pt
);
if
(
a
)
{
QListWidgetItem
*
item
=
ui
->
listWidget_files
->
currentItem
();
if
(
a
==
a_history
)
{
execFileHistory
(
item
);
}
else
if
(
a
==
a_properties
)
{
execFilePropertyDialog
(
item
);
}
}
}
void
MainWindow
::
on_listWidget_unstaged_customContextMenuRequested
(
const
QPoint
&
pos
)
{
GitPtr
g
=
git
();
if
(
!
isValidWorkingCopy
(
g
))
return
;
QList
<
QListWidgetItem
*>
items
=
ui
->
listWidget_unstaged
->
selectedItems
();
if
(
!
items
.
isEmpty
())
{
QMenu
menu
;
QAction
*
a_stage
=
menu
.
addAction
(
tr
(
"Stage"
));
QAction
*
a_revert
=
menu
.
addAction
(
tr
(
"Revert"
));
QAction
*
a_ignore
=
menu
.
addAction
(
tr
(
"Ignore"
));
QAction
*
a_delete
=
menu
.
addAction
(
tr
(
"Delete"
));
QAction
*
a_history
=
menu
.
addAction
(
tr
(
"History"
));
QAction
*
a_properties
=
addMenuActionProperties
(
&
menu
);
QPoint
pt
=
ui
->
listWidget_unstaged
->
mapToGlobal
(
pos
)
+
QPoint
(
8
,
-8
);
QAction
*
a
=
menu
.
exec
(
pt
);
if
(
a
)
{
QListWidgetItem
*
item
=
ui
->
listWidget_unstaged
->
currentItem
();
if
(
a
==
a_stage
)
{
for_each_selected_unstaged_files
([
&
](
QString
const
&
path
){
g
->
stage
(
path
);
});
updateHeadFilesList
(
true
);
}
else
if
(
a
==
a_revert
)
{
QStringList
paths
;
for_each_selected_unstaged_files
([
&
](
QString
const
&
path
){
paths
.
push_back
(
path
);
});
revertFile
(
paths
);
}
else
if
(
a
==
a_ignore
)
{
QString
append
;
for_each_selected_unstaged_files
([
&
](
QString
const
&
path
){
if
(
path
==
".gitignore"
)
{
// skip
}
else
{
append
+=
path
+
'\n'
;
}
});
QString
gitignore_path
=
currentWorkingCopyDir
()
/
".gitignore"
;
if
(
TextEditDialog
::
editFile
(
this
,
gitignore_path
,
".gitignore"
,
append
))
{
updateHeadFilesList
(
true
);
}
}
else
if
(
a
==
a_delete
)
{
if
(
askAreYouSureYouWantToRun
(
"Delete"
,
"Delete selected files."
))
{
for_each_selected_unstaged_files
([
&
](
QString
const
&
path
){
g
->
removeFile
(
path
);
g
->
chdirexec
([
&
](){
QFile
(
path
).
remove
();
return
true
;
});
});
openRepository
();
}
}
else
if
(
a
==
a_history
)
{
execFileHistory
(
item
);
}
else
if
(
a
==
a_properties
)
{
execFilePropertyDialog
(
item
);
}
}
}
}
void
MainWindow
::
on_listWidget_staged_customContextMenuRequested
(
const
QPoint
&
pos
)
{
GitPtr
g
=
git
();
if
(
!
isValidWorkingCopy
(
g
))
return
;
QListWidgetItem
*
item
=
ui
->
listWidget_staged
->
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_properties
=
addMenuActionProperties
(
&
menu
);
QPoint
pt
=
ui
->
listWidget_staged
->
mapToGlobal
(
pos
)
+
QPoint
(
8
,
-8
);
QAction
*
a
=
menu
.
exec
(
pt
);
if
(
a
)
{
QListWidgetItem
*
item
=
ui
->
listWidget_unstaged
->
currentItem
();
if
(
a
==
a_unstage
)
{
g
->
unstage
(
path
);
updateHeadFilesList
(
true
);
}
else
if
(
a
==
a_history
)
{
execFileHistory
(
item
);
}
else
if
(
a
==
a_properties
)
{
execFilePropertyDialog
(
item
);
}
}
}
}
}
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
;
}
void
MainWindow
::
revertFile
(
QStringList
const
&
paths
)
{
GitPtr
g
=
git
();
if
(
!
isValidWorkingCopy
(
g
))
return
;
if
(
paths
.
isEmpty
())
{
// nop
}
else
{
QString
cmd
=
"git checkout --
\"
"
+
paths
[
0
]
+
'\"'
;
if
(
askAreYouSureYouWantToRun
(
tr
(
"Revert a file"
),
"> "
+
cmd
))
{
for
(
QString
const
&
path
:
paths
)
{
g
->
revertFile
(
path
);
}
openRepository
();
}
}
}
void
MainWindow
::
revertAllFiles
()
{
GitPtr
g
=
git
();
if
(
!
isValidWorkingCopy
(
g
))
return
;
QString
cmd
=
"git reset --hard HEAD"
;
if
(
askAreYouSureYouWantToRun
(
tr
(
"Revert all files"
),
"> "
+
cmd
))
{
g
->
revertAllFiles
();
openRepository
();
}
}
QStringList
MainWindow
::
selectedStagedFiles
()
const
{
QStringList
list
;
QList
<
QListWidgetItem
*>
items
=
ui
->
listWidget_staged
->
selectedItems
();
for
(
QListWidgetItem
*
item
:
items
)
{
QString
path
=
getFilePath
(
item
);
list
.
push_back
(
path
);
}
return
list
;
}
QStringList
MainWindow
::
selectedUnstagedFiles
()
const
{
QStringList
list
;
QList
<
QListWidgetItem
*>
items
=
ui
->
listWidget_unstaged
->
selectedItems
();
for
(
QListWidgetItem
*
item
:
items
)
{
QString
path
=
getFilePath
(
item
);
list
.
push_back
(
path
);
}
return
list
;
}
void
MainWindow
::
for_each_selected_staged_files
(
std
::
function
<
void
(
QString
const
&
)
>
fn
)
{
for
(
QString
path
:
selectedStagedFiles
())
{
fn
(
path
);
}
}
void
MainWindow
::
for_each_selected_unstaged_files
(
std
::
function
<
void
(
QString
const
&
)
>
fn
)
{
for
(
QString
path
:
selectedUnstagedFiles
())
{
fn
(
path
);
}
}
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
::
execFileHistory
(
QListWidgetItem
*
item
)
{
if
(
item
)
{
QString
path
=
getFilePath
(
item
);
if
(
!
path
.
isEmpty
())
{
execFileHistory
(
path
);
}
}
}
void
MainWindow
::
on_action_open_existing_working_copy_triggered
()
{
QString
dir
=
defaultWorkingDir
();
dir
=
QFileDialog
::
getExistingDirectory
(
this
,
tr
(
"Add existing working copy"
),
dir
);
RepositoryItem
item
;
item
.
local_dir
=
dir
;
item
.
name
=
makeRepositoryName
(
dir
);
saveRepositoryBookmark
(
item
);
}
void
MainWindow
::
on_action_view_refresh_triggered
()
{
openRepository
();
}
void
MainWindow
::
on_tableWidget_log_currentItemChanged
(
QTableWidgetItem
*
/*current*/
,
QTableWidgetItem
*
/*previous*/
)
{
stopDiff
();
clearFileList
();
QTableWidgetItem
*
item
=
ui
->
tableWidget_log
->
item
(
selectedLogIndex
(),
0
);
if
(
!
item
)
return
;
int
row
=
item
->
data
(
IndexRole
).
toInt
();
if
(
row
<
(
int
)
pv
->
logs
.
size
())
{
Git
::
CommitItem
const
&
commit
=
pv
->
logs
[
row
];
if
(
Git
::
isUncommited
(
commit
))
{
updateHeadFilesList
(
false
);
}
else
{
GitPtr
g
=
git
();
if
(
isValidWorkingCopy
(
g
))
{
QString
id
=
commit
.
commit_id
;
startDiff
(
g
,
id
);
}
}
pv
->
update_files_list_counter
=
200
;
}
}
void
MainWindow
::
on_toolButton_stage_clicked
()
{
GitPtr
g
=
git
();
if
(
!
isValidWorkingCopy
(
g
))
return
;
g
->
stage
(
selectedUnstagedFiles
());
updateHeadFilesList
(
true
);
}
void
MainWindow
::
on_toolButton_unstage_clicked
()
{
GitPtr
g
=
git
();
if
(
!
isValidWorkingCopy
(
g
))
return
;
g
->
unstage
(
selectedStagedFiles
());
updateHeadFilesList
(
true
);
}
void
MainWindow
::
on_toolButton_select_all_clicked
()
{
if
(
ui
->
listWidget_unstaged
->
count
()
>
0
)
{
ui
->
listWidget_unstaged
->
setFocus
();
ui
->
listWidget_unstaged
->
selectAll
();
}
else
if
(
ui
->
listWidget_staged
->
count
()
>
0
)
{
ui
->
listWidget_staged
->
setFocus
();
ui
->
listWidget_staged
->
selectAll
();
}
}
void
MainWindow
::
on_toolButton_commit_clicked
()
{
ui
->
action_commit
->
trigger
();
}
bool
MainWindow
::
editFile
(
const
QString
&
path
,
QString
const
&
title
)
{
return
TextEditDialog
::
editFile
(
this
,
path
,
title
);
}
struct
Task
{
int
index
=
0
;
int
parent
=
0
;
Task
()
{
}
Task
(
int
index
,
int
parent
)
:
index
(
index
)
,
parent
(
parent
)
{
}
};
struct
Element
{
int
depth
=
0
;
std
::
vector
<
int
>
indexes
;
};
void
MainWindow::updateCommitGraph
()
{
const
size_t
LogCount
=
pv
->
logs
.
size
();
// 樹形図情報を構築する
if
(
LogCount
>
0
)
{
auto
LogItem
=
[
&
](
size_t
i
)
->
Git
::
CommitItem
&
{
return
pv
->
logs
[
i
];
};
enum
{
// 有向グラフを構築するあいだ CommitItem::marker_depth をフラグとして使用する
UNKNOWN
=
0
,
KNOWN
=
1
,
};
for
(
Git
::
CommitItem
&
item
:
pv
->
logs
)
{
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
.
push_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
.
push_back
(
Task
(
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
.
push_back
(
Task
(
index
,
i
));
// タスク追加
}
index
=
LogItem
(
index
).
parent_lines
[
0
].
index
;
// 次の親(親リストの先頭の要素)
}
if
(
e
.
indexes
.
size
()
>=
2
)
{
elements
.
push_back
(
e
);
}
}
}
// 線情報をクリア
for
(
Git
::
CommitItem
&
item
:
pv
->
logs
)
{
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
(
size_t
i
=
0
;
i
<
elements
.
size
();
i
++
)
{
Element
const
&
e
=
elements
[
i
];
auto
ColorNumber
=
[
&
](){
return
e
.
depth
;
};
size_t
count
=
e
.
indexes
.
size
();
for
(
size_t
j
=
0
;
j
+
1
<
count
;
j
++
)
{
int
curr
=
e
.
indexes
[
j
];
int
next
=
e
.
indexes
[
j
+
1
];
TreeLine
line
(
next
,
e
.
depth
);
line
.
color_number
=
ColorNumber
();
line
.
bend_early
=
(
j
+
2
<
count
||
!
LogItem
(
next
).
resolved
);
if
(
j
+
2
==
count
)
{
if
(
count
>
2
||
!
LogItem
(
curr
).
has_child
)
{
// 直結ではない、または、子がない
line
.
depth
=
LogItem
(
next
).
marker_depth
;
// 合流する先のアイテムの深さと同じにする
}
}
LogItem
(
curr
).
parent_lines
.
push_back
(
line
);
// 線を追加
}
}
}
}
}
QList
<
Git
::
Branch
>
MainWindow
::
findBranch
(
QString
const
&
id
)
{
auto
it
=
pv
->
branch_map
.
find
(
id
);
if
(
it
!=
pv
->
branch_map
.
end
())
{
return
it
->
second
;
}
return
QList
<
Git
::
Branch
>
();
}
QList
<
Git
::
Tag
>
MainWindow
::
findTag
(
QString
const
&
id
)
{
auto
it
=
pv
->
tag_map
.
find
(
id
);
if
(
it
!=
pv
->
tag_map
.
end
())
{
return
it
->
second
;
}
return
QList
<
Git
::
Tag
>
();
}
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"
;
editFile
(
path
,
".gitignore"
);
}
int
MainWindow
::
selectedLogIndex
()
const
{
int
i
=
ui
->
tableWidget_log
->
currentRow
();
if
(
i
>=
0
&&
i
<
(
int
)
pv
->
logs
.
size
())
{
return
i
;
}
return
-1
;
}
Git
::
CommitItem
const
*
MainWindow
::
selectedCommitItem
()
const
{
int
i
=
selectedLogIndex
();
if
(
i
>=
0
&&
i
<
(
int
)
pv
->
logs
.
size
())
{
return
&
pv
->
logs
[
i
];
}
return
nullptr
;
}
Git
::
Object
MainWindow
::
cat_file_
(
GitPtr
g
,
QString
const
&
id
)
{
if
(
isValidWorkingCopy
(
g
))
{
QString
path_prefix
=
PATH_PREFIX
;
if
(
id
.
startsWith
(
path_prefix
))
{
QString
path
=
g
->
workingRepositoryDir
();
path
=
path
/
id
.
mid
(
path_prefix
.
size
());
QFile
file
(
path
);
if
(
file
.
open
(
QFile
::
ReadOnly
))
{
Git
::
Object
obj
;
obj
.
type
==
Git
::
Object
::
Type
::
UNKNOWN
;
obj
.
content
=
file
.
readAll
();
return
obj
;
}
}
else
if
(
Git
::
isValidID
(
id
))
{
return
pv
->
objcache
.
catFile
(
id
);;
}
}
return
Git
::
Object
();
}
Git
::
Object
MainWindow
::
cat_file
(
QString
const
&
id
)
{
return
cat_file_
(
git
(),
id
);
}
void
MainWindow
::
updateDiffView
(
QListWidgetItem
*
item
)
{
clearDiffView
();
int
idiff
=
indexOfDiff
(
item
);
if
(
idiff
>=
0
&&
idiff
<
pv
->
diff
.
result
.
size
())
{
Git
::
Diff
const
&
diff
=
pv
->
diff
.
result
[
idiff
];
QString
key
=
GitDiff
::
makeKey
(
diff
);
auto
it
=
pv
->
diff_cache
.
find
(
key
);
if
(
it
!=
pv
->
diff_cache
.
end
())
{
int
row
=
ui
->
tableWidget_log
->
currentRow
();
bool
uncommited
=
(
row
>=
0
&&
row
<
(
int
)
pv
->
logs
.
size
()
&&
Git
::
isUncommited
(
pv
->
logs
[
row
]));
ui
->
widget_diff_view
->
updateDiffView
(
it
->
second
,
uncommited
);
}
}
}
void
MainWindow
::
updateUnstagedFileCurrentItem
()
{
updateDiffView
(
ui
->
listWidget_unstaged
->
currentItem
());
}
void
MainWindow
::
updateStagedFileCurrentItem
()
{
updateDiffView
(
ui
->
listWidget_staged
->
currentItem
());
}
void
MainWindow
::
on_listWidget_unstaged_currentRowChanged
(
int
/*currentRow*/
)
{
updateUnstagedFileCurrentItem
();
}
void
MainWindow
::
on_listWidget_staged_currentRowChanged
(
int
/*currentRow*/
)
{
updateStagedFileCurrentItem
();
}
void
MainWindow
::
on_listWidget_files_currentRowChanged
(
int
/*currentRow*/
)
{
updateDiffView
(
ui
->
listWidget_files
->
currentItem
());
}
void
MainWindow
::
setGitCommand
(
QString
const
&
path
,
bool
save
)
{
if
(
save
)
{
MySettings
s
;
s
.
beginGroup
(
"Global"
);
s
.
setValue
(
"GitCommand"
,
path
);
s
.
endGroup
();
}
pv
->
gcx
.
git_command
=
path
;
}
void
MainWindow
::
setFileCommand
(
QString
const
&
path
,
bool
save
)
{
if
(
save
)
{
MySettings
s
;
s
.
beginGroup
(
"Global"
);
s
.
setValue
(
"FileCommand"
,
path
);
s
.
endGroup
();
}
pv
->
file_command
=
path
;
}
QStringList
MainWindow
::
whichCommand_
(
QString
const
&
cmdfile
)
{
QStringList
list
;
std
::
vector
<
std
::
string
>
vec
;
FileUtil
::
which
(
cmdfile
.
toStdString
(),
&
vec
);
for
(
std
::
string
const
&
s
:
vec
)
{
list
.
push_back
(
QString
::
fromStdString
(
s
));
}
return
list
;
}
QString
MainWindow
::
selectCommand_
(
QString
const
&
cmdname
,
QString
const
&
cmdfile
,
QStringList
const
&
list
,
QString
path
,
std
::
function
<
void
(
QString
const
&
)
>
callback
)
{
QString
window_title
=
tr
(
"Select %1 command"
);
window_title
=
window_title
.
arg
(
cmdfile
);
SelectCommandDialog
dlg
(
this
,
cmdname
,
cmdfile
,
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
::
selectGitCommand
()
{
#ifdef Q_OS_WIN
char
const
*
exe
=
"git.exe"
;
#else
char
const
*
exe
=
"git"
;
#endif
QString
path
=
pv
->
gcx
.
git_command
;
auto
fn
=
[
&
](
QString
const
&
path
){
setGitCommand
(
path
,
true
);
};
QStringList
list
=
whichCommand_
(
exe
);
#ifdef Q_OS_WIN
{
QStringList
newlist
;
QString
suffix1
=
"
\\
bin
\\
git.exe"
;
QString
suffix2
=
"
\\
cmd
\\
git.exe"
;
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.exe"
;
if
(
QFileInfo
(
t1
).
isExecutable
())
newlist
.
push_back
(
t1
);
QString
t2
=
t
+
"
\\
mingw
\\
bin
\\
git.exe"
;
if
(
QFileInfo
(
t2
).
isExecutable
())
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
()
{
#ifdef Q_OS_WIN
char
const
*
exe
=
"file.exe"
;
#else
char
const
*
exe
=
"file"
;
#endif
QString
path
=
pv
->
file_command
;
auto
fn
=
[
&
](
QString
const
&
path
){
setFileCommand
(
path
,
true
);
};
QStringList
list
=
whichCommand_
(
exe
);
#ifdef Q_OS_WIN
QString
dir
=
getModuleFileDir
();
QString
path1
=
dir
/
"msys/file.exe"
;
QString
path2
;
int
i
=
dir
.
lastIndexOf
(
'/'
);
int
j
=
dir
.
lastIndexOf
(
'\\'
);
if
(
i
<
j
)
i
=
j
;
if
(
i
>
0
)
{
path2
=
dir
.
mid
(
0
,
i
)
/
"msys/file.exe"
;
}
path1
=
misc
::
normalizePathSeparator
(
path1
);
path2
=
misc
::
normalizePathSeparator
(
path2
);
if
(
QFileInfo
(
path1
).
isExecutable
())
list
.
push_back
(
path1
);
if
(
QFileInfo
(
path2
).
isExecutable
())
list
.
push_back
(
path2
);
#endif
return
selectCommand_
(
"File"
,
exe
,
list
,
path
,
fn
);
}
void
MainWindow
::
checkGitCommand
()
{
while
(
1
)
{
QFileInfo
info
(
pv
->
gcx
.
git_command
);
if
(
info
.
isExecutable
())
{
break
;
// ok
}
if
(
selectGitCommand
().
isEmpty
())
{
close
();
break
;
}
}
}
void
MainWindow
::
checkFileCommand
()
{
while
(
1
)
{
QFileInfo
info
(
pv
->
file_command
);
if
(
info
.
isExecutable
())
{
break
;
// ok
}
if
(
selectFileCommand
().
isEmpty
())
{
close
();
break
;
}
}
}
void
MainWindow
::
timerEvent
(
QTimerEvent
*
)
{
pv
->
interval_250ms_counter
+=
pv
->
timer_interval_ms
;
if
(
pv
->
interval_250ms_counter
>=
250
)
{
pv
->
interval_250ms_counter
-=
250
;
checkGitCommand
();
checkFileCommand
();
}
if
(
pv
->
update_files_list_counter
>
0
)
{
if
(
pv
->
update_files_list_counter
>
pv
->
timer_interval_ms
)
{
pv
->
update_files_list_counter
-=
pv
->
timer_interval_ms
;
}
else
{
pv
->
update_files_list_counter
=
0
;
updateCurrentFilesList
();
}
}
}
bool
MainWindow
::
event
(
QEvent
*
event
)
{
return
QMainWindow
::
event
(
event
);
}
bool
MainWindow
::
eventFilter
(
QObject
*
watched
,
QEvent
*
event
)
{
if
(
event
->
type
()
==
QEvent
::
KeyPress
)
{
QKeyEvent
*
e
=
dynamic_cast
<
QKeyEvent
*>
(
event
);
Q_ASSERT
(
e
);
int
k
=
e
->
key
();
if
(
watched
==
ui
->
treeWidget_repos
)
{
if
(
k
==
Qt
::
Key_Enter
||
k
==
Qt
::
Key_Return
)
{
openSelectedRepository
();
return
true
;
}
}
else
if
(
watched
==
ui
->
tableWidget_log
)
{
if
(
k
==
Qt
::
Key_Home
)
{
ui
->
tableWidget_log
->
setCurrentCell
(
0
,
0
);
return
true
;
}
}
}
else
if
(
event
->
type
()
==
QEvent
::
FocusIn
)
{
// ファイルリストがフォーカスを得たとき、diffビューを更新する。(コンテキストメニュー対応)
if
(
watched
==
ui
->
listWidget_unstaged
)
{
updateUnstagedFileCurrentItem
();
return
true
;
}
if
(
watched
==
ui
->
listWidget_staged
)
{
updateStagedFileCurrentItem
();
return
true
;
}
}
return
false
;
}
bool
MainWindow
::
saveByteArrayAs
(
QByteArray
const
&
ba
,
QString
const
&
dstpath
)
{
QFile
file
(
dstpath
);
if
(
file
.
open
(
QFile
::
WriteOnly
))
{
file
.
write
(
ba
);
file
.
close
();
return
true
;
}
else
{
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
)
{
Git
::
Object
obj
=
cat_file
(
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
;
}
bool
MainWindow
::
saveAs
(
QString
const
&
id
,
QString
const
&
dstpath
)
{
if
(
id
.
startsWith
(
PATH_PREFIX
))
{
return
saveFileAs
(
id
.
mid
(
1
),
dstpath
);
}
else
{
return
saveBlobAs
(
id
,
dstpath
);
}
}
QString
MainWindow
::
saveAsTemp
(
QString
const
&
id
)
{
QString
path
=
newTempFilePath
();
saveAs
(
id
,
path
);
return
path
;
}
void
MainWindow
::
on_action_edit_settings_triggered
()
{
SettingsDialog
dlg
(
this
);
if
(
dlg
.
exec
()
==
QDialog
::
Accepted
)
{
pv
->
appsettings
=
dlg
.
settings
();
setGitCommand
(
pv
->
appsettings
.
git_command
,
false
);
setFileCommand
(
pv
->
appsettings
.
file_command
,
false
);
}
}
void
MainWindow
::
on_action_branch_new_triggered
()
{
GitPtr
g
=
git
();
if
(
!
isValidWorkingCopy
(
g
))
return
;
NewBranchDialog
dlg
(
this
);
if
(
dlg
.
exec
()
==
QDialog
::
Accepted
)
{
QString
name
=
dlg
.
branchName
();
g
->
createBranch
(
name
);
}
}
void
MainWindow
::
on_action_branch_checkout_triggered
()
{
GitPtr
g
=
git
();
if
(
!
isValidWorkingCopy
(
g
))
return
;
QList
<
Git
::
Branch
>
branches
=
g
->
branches
();
CheckoutBranchDialog
dlg
(
this
,
branches
);
if
(
dlg
.
exec
()
==
QDialog
::
Accepted
)
{
QString
name
=
dlg
.
branchName
();
if
(
!
name
.
isEmpty
())
{
g
->
checkoutBranch
(
name
);
openRepository
();
}
}
}
void
MainWindow
::
on_action_branch_merge_triggered
()
{
GitPtr
g
=
git
();
if
(
!
isValidWorkingCopy
(
g
))
return
;
QList
<
Git
::
Branch
>
branches
=
g
->
branches
();
MergeBranchDialog
dlg
(
this
,
branches
);
if
(
dlg
.
exec
()
==
QDialog
::
Accepted
)
{
QString
name
=
dlg
.
branchName
();
if
(
!
name
.
isEmpty
())
{
g
->
mergeBranch
(
name
);
}
}
}
void
MainWindow
::
on_action_clone_triggered
()
{
GitPtr
g
=
std
::
shared_ptr
<
Git
>
(
new
Git
(
pv
->
gcx
,
QString
()));
CloneDialog
dlg
(
this
,
g
,
defaultWorkingDir
());
if
(
dlg
.
exec
()
==
QDialog
::
Accepted
)
{
QString
dir
=
dlg
.
workingDir
();
RepositoryItem
item
;
item
.
local_dir
=
dir
;
item
.
name
=
makeRepositoryName
(
dir
);
saveRepositoryBookmark
(
item
);
pv
->
current_repo
=
item
;
openRepository
();
}
}
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
::
on_comboBox_filter_currentTextChanged
(
const
QString
&
text
)
{
pv
->
repository_filter_text
=
text
;
updateRepositoriesList
();
}
void
MainWindow
::
on_toolButton_erase_filter_clicked
()
{
ui
->
comboBox_filter
->
clearEditText
();
ui
->
treeWidget_repos
->
setFocus
();
}
void
MainWindow
::
deleteTags
(
QStringList
const
&
tagnames
)
{
GitPtr
g
=
git
();
if
(
!
isValidWorkingCopy
(
g
))
return
;
if
(
!
tagnames
.
isEmpty
())
{
reopenRepository
([
&
](
GitPtr
g
){
for
(
QString
const
&
name
:
tagnames
)
{
g
->
delete_tag
(
name
,
true
);
}
});
}
}
void
MainWindow
::
deleteTags
(
const
Git
::
CommitItem
&
commit
)
{
auto
it
=
pv
->
tag_map
.
find
(
commit
.
commit_id
);
if
(
it
!=
pv
->
tag_map
.
end
())
{
QStringList
names
;
QList
<
Git
::
Tag
>
const
&
tags
=
it
->
second
;
for
(
Git
::
Tag
const
&
tag
:
tags
)
{
names
.
push_back
(
tag
.
name
);
}
deleteTags
(
names
);
}
}
void
MainWindow
::
deleteSelectedTags
()
{
Git
::
CommitItem
const
*
commit
=
selectedCommitItem
();
if
(
commit
)
{
QList
<
Git
::
Tag
>
list
=
findTag
(
commit
->
commit_id
);
if
(
!
list
.
isEmpty
())
{
DeleteTagsDialog
dlg
(
this
,
list
);
if
(
dlg
.
exec
()
==
QDialog
::
Accepted
)
{
QStringList
list
=
dlg
.
selectedTags
();
deleteTags
(
list
);
}
}
}
}
void
MainWindow
::
addTag
()
{
GitPtr
g
=
git
();
if
(
!
isValidWorkingCopy
(
g
))
return
;
QString
commit_id
;
Git
::
CommitItem
const
*
commit
=
selectedCommitItem
();
if
(
commit
&&
!
commit
->
commit_id
.
isEmpty
())
{
commit_id
=
commit
->
commit_id
;
}
EditTagDialog
dlg
(
this
);
if
(
dlg
.
exec
()
==
QDialog
::
Accepted
)
{
reopenRepository
([
&
](
GitPtr
g
){
g
->
tag
(
dlg
.
text
(),
commit_id
);
if
(
dlg
.
isPushChecked
())
{
g
->
push
(
true
);
}
});
}
}
void
MainWindow
::
on_action_tag_triggered
()
{
addTag
();
}
void
MainWindow
::
on_action_tag_push_all_triggered
()
{
reopenRepository
([
&
](
GitPtr
g
){
g
->
push
(
true
);
});
}
void
MainWindow
::
on_action_tag_delete_triggered
()
{
deleteSelectedTags
();
}
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
);
qDebug
()
<<
path
;
}
}
QString
MainWindow
::
newTempFilePath
()
{
QString
tmpdir
=
QStandardPaths
::
writableLocation
(
QStandardPaths
::
TempLocation
);
QString
path
=
tmpdir
/
tempfileHeader
()
+
QString
::
number
(
pv
->
temp_file_counter
);
pv
->
temp_file_counter
++
;
return
path
;
}
QString
MainWindow
::
determinFileType_
(
QString
const
&
path
,
bool
mime
,
std
::
function
<
void
(
QString
const
&
cmd
,
QByteArray
*
ba
)
>
callback
)
{
// ファイルタイプを調べる
if
(
QFileInfo
(
pv
->
file_command
).
isExecutable
())
{
QString
file
=
pv
->
file_command
;
QString
mgc
;
#ifdef Q_OS_WIN
int
i
=
file
.
lastIndexOf
(
'/'
);
int
j
=
file
.
lastIndexOf
(
'\\'
);
if
(
i
<
j
)
i
=
j
;
if
(
i
>=
0
)
{
mgc
=
file
.
mid
(
0
,
i
+
1
)
+
"magic.mgc"
;
if
(
QFileInfo
(
mgc
).
isReadable
())
{
// ok
}
else
{
mgc
=
QString
();
}
}
#endif
QString
cmd
;
if
(
mgc
.
isEmpty
())
{
cmd
=
"
\"
%1
\"
"
;
cmd
=
cmd
.
arg
(
file
);
}
else
{
cmd
=
"
\"
%1
\"
-m
\"
%2
\"
"
;
cmd
=
cmd
.
arg
(
file
).
arg
(
mgc
);
}
if
(
mime
)
{
cmd
+=
" --mime"
;
}
cmd
+=
QString
(
" --brief
\"
%1
\"
"
).
arg
(
path
);
// run file command
QByteArray
ba
;
callback
(
cmd
,
&
ba
);
// parse file type
if
(
!
ba
.
isEmpty
())
{
QString
s
=
QString
::
fromUtf8
(
ba
).
trimmed
();
QStringList
list
=
s
.
split
(
';'
,
QString
::
SkipEmptyParts
);
if
(
!
list
.
isEmpty
())
{
QString
mimetype
=
list
[
0
].
trimmed
();
qDebug
()
<<
mimetype
;
return
mimetype
;
}
}
return
cmd
;
}
else
{
qDebug
()
<<
"No executable 'file' command"
;
}
return
QString
();
}
QString
MainWindow
::
determinFileType
(
QString
const
&
path
,
bool
mime
)
{
return
determinFileType_
(
path
,
mime
,
[](
QString
const
&
cmd
,
QByteArray
*
ba
){
misc
::
runCommand
(
cmd
,
ba
);
});
}
QString
MainWindow
::
determinFileType
(
QByteArray
const
&
in
,
bool
mime
)
{
return
determinFileType_
(
"-"
,
mime
,
[
&
](
QString
const
&
cmd
,
QByteArray
*
ba
){
misc
::
runCommand
(
cmd
,
&
in
,
ba
);
});
}
void
MainWindow
::
on_tableWidget_log_itemDoubleClicked
(
QTableWidgetItem
*
)
{
Git
::
CommitItem
const
*
commit
=
selectedCommitItem
();
if
(
commit
)
{
execCommitPropertyDialog
(
commit
);
}
}
void
MainWindow
::
execFilePropertyDialog
(
QListWidgetItem
*
item
)
{
if
(
item
)
{
QString
path
=
getFilePath
(
item
);
QString
id
=
getObjectID
(
item
);
FilePropertyDialog
dlg
(
this
);
dlg
.
exec
(
this
,
path
,
id
);
}
}
void
MainWindow
::
on_listWidget_unstaged_itemDoubleClicked
(
QListWidgetItem
*
item
)
{
execFilePropertyDialog
(
item
);
}
void
MainWindow
::
on_listWidget_staged_itemDoubleClicked
(
QListWidgetItem
*
item
)
{
execFilePropertyDialog
(
item
);
}
void
MainWindow
::
on_listWidget_files_itemDoubleClicked
(
QListWidgetItem
*
item
)
{
execFilePropertyDialog
(
item
);
}
QPixmap
MainWindow
::
getTransparentPixmap
()
{
if
(
pv
->
transparent_pixmap
.
isNull
())
{
pv
->
transparent_pixmap
=
QPixmap
(
":/image/transparent.png"
);
}
return
pv
->
transparent_pixmap
;
}
QString
MainWindow
::
getCommitIdFromTag
(
QString
const
&
tag
)
{
return
pv
->
objcache
.
getCommitIdFromTag
(
tag
);
}
void
MainWindow
::
on_action_test_triggered
()
{
qDebug
()
<<
getCommitIdFromTag
(
"tag2"
);
}
File Metadata
Details
Attached
Mime Type
text/x-c
Expires
Sun, Nov 15, 4:07 AM (1 d, 22 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
134092
Default Alt Text
MainWindow.cpp (62 KB)
Attached To
Mode
R77 Guitar
Attached
Detach File
Event Timeline