GitWidget.cpp 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. /*
  2. * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
  3. * All rights reserved.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are met:
  7. *
  8. * 1. Redistributions of source code must retain the above copyright notice, this
  9. * list of conditions and the following disclaimer.
  10. *
  11. * 2. Redistributions in binary form must reproduce the above copyright notice,
  12. * this list of conditions and the following disclaimer in the documentation
  13. * and/or other materials provided with the distribution.
  14. *
  15. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  16. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  17. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  18. * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
  19. * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  20. * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  21. * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  22. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  23. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  24. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  25. */
  26. #include "GitWidget.h"
  27. #include "GitFilesModel.h"
  28. #include <AK/LogStream.h>
  29. #include <LibCore/File.h>
  30. #include <LibDiff/Format.h>
  31. #include <LibGUI/Application.h>
  32. #include <LibGUI/BoxLayout.h>
  33. #include <LibGUI/Button.h>
  34. #include <LibGUI/InputBox.h>
  35. #include <LibGUI/Label.h>
  36. #include <LibGUI/MessageBox.h>
  37. #include <LibGUI/Model.h>
  38. #include <LibGUI/Painter.h>
  39. #include <LibGfx/Bitmap.h>
  40. #include <stdio.h>
  41. namespace HackStudio {
  42. GitWidget::GitWidget(const LexicalPath& repo_root)
  43. : m_repo_root(repo_root)
  44. {
  45. set_layout<GUI::HorizontalBoxLayout>();
  46. auto& unstaged = add<GUI::Widget>();
  47. unstaged.set_layout<GUI::VerticalBoxLayout>();
  48. auto& unstaged_header = unstaged.add<GUI::Widget>();
  49. unstaged_header.set_layout<GUI::HorizontalBoxLayout>();
  50. auto& refresh_button = unstaged_header.add<GUI::Button>();
  51. refresh_button.set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/reload.png"));
  52. refresh_button.set_fixed_size(16, 16);
  53. refresh_button.set_tooltip("refresh");
  54. refresh_button.on_click = [this](int) { refresh(); };
  55. auto& unstaged_label = unstaged_header.add<GUI::Label>();
  56. unstaged_label.set_text("Unstaged");
  57. unstaged_header.set_fixed_height(20);
  58. m_unstaged_files = unstaged.add<GitFilesView>(
  59. [this](const auto& file) { stage_file(file); },
  60. Gfx::Bitmap::load_from_file("/res/icons/16x16/plus.png").release_nonnull());
  61. m_unstaged_files->on_selection = [this](const GUI::ModelIndex& index) {
  62. const auto& selected = index.data().as_string();
  63. show_diff(LexicalPath(selected));
  64. };
  65. auto& staged = add<GUI::Widget>();
  66. staged.set_layout<GUI::VerticalBoxLayout>();
  67. auto& staged_header = staged.add<GUI::Widget>();
  68. staged_header.set_layout<GUI::HorizontalBoxLayout>();
  69. auto& commit_button = staged_header.add<GUI::Button>();
  70. commit_button.set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/commit.png"));
  71. commit_button.set_fixed_size(16, 16);
  72. commit_button.set_tooltip("commit");
  73. commit_button.on_click = [this](int) { commit(); };
  74. auto& staged_label = staged_header.add<GUI::Label>();
  75. staged_label.set_text("Staged");
  76. staged_header.set_fixed_height(20);
  77. m_staged_files = staged.add<GitFilesView>(
  78. [this](const auto& file) { unstage_file(file); },
  79. Gfx::Bitmap::load_from_file("/res/icons/16x16/minus.png").release_nonnull());
  80. }
  81. bool GitWidget::initialize()
  82. {
  83. auto result = GitRepo::try_to_create(m_repo_root);
  84. switch (result.type) {
  85. case GitRepo::CreateResult::Type::Success:
  86. m_git_repo = result.repo;
  87. return true;
  88. case GitRepo::CreateResult::Type::GitProgramNotFound:
  89. GUI::MessageBox::show(window(), "Please install the Git port", "Error", GUI::MessageBox::Type::Error);
  90. return false;
  91. case GitRepo::CreateResult::Type::NoGitRepo: {
  92. auto decision = GUI::MessageBox::show(window(), "Create git repository?", "Git", GUI::MessageBox::Type::Question, GUI::MessageBox::InputType::YesNo);
  93. if (decision != GUI::Dialog::ExecResult::ExecYes)
  94. return false;
  95. m_git_repo = GitRepo::initialize_repository(m_repo_root);
  96. return true;
  97. }
  98. default:
  99. ASSERT_NOT_REACHED();
  100. }
  101. }
  102. bool GitWidget::initialize_if_needed()
  103. {
  104. if (initialized())
  105. return true;
  106. return initialize();
  107. }
  108. void GitWidget::refresh()
  109. {
  110. if (!initialize_if_needed()) {
  111. dbgln("GitWidget initialization failed");
  112. return;
  113. }
  114. ASSERT(!m_git_repo.is_null());
  115. m_unstaged_files->set_model(GitFilesModel::create(m_git_repo->unstaged_files()));
  116. m_staged_files->set_model(GitFilesModel::create(m_git_repo->staged_files()));
  117. }
  118. void GitWidget::stage_file(const LexicalPath& file)
  119. {
  120. dbgln("staging: {}", file);
  121. bool rc = m_git_repo->stage(file);
  122. ASSERT(rc);
  123. refresh();
  124. }
  125. void GitWidget::unstage_file(const LexicalPath& file)
  126. {
  127. dbgln("unstaging: {}", file);
  128. bool rc = m_git_repo->unstage(file);
  129. ASSERT(rc);
  130. refresh();
  131. }
  132. void GitWidget::commit()
  133. {
  134. String message;
  135. auto res = GUI::InputBox::show(window(), message, "Commit message:", "Commit");
  136. if (res != GUI::InputBox::ExecOK || message.is_empty())
  137. return;
  138. dbgln("commit message: {}", message);
  139. m_git_repo->commit(message);
  140. refresh();
  141. }
  142. void GitWidget::set_view_diff_callback(ViewDiffCallback callback)
  143. {
  144. m_view_diff_callback = move(callback);
  145. }
  146. void GitWidget::show_diff(const LexicalPath& file_path)
  147. {
  148. if (!m_git_repo->is_tracked(file_path)) {
  149. auto file = Core::File::construct(file_path.string());
  150. if (!file->open(Core::IODevice::ReadOnly)) {
  151. perror("open");
  152. ASSERT_NOT_REACHED();
  153. }
  154. auto content = file->read_all();
  155. String content_string((char*)content.data(), content.size());
  156. m_view_diff_callback("", Diff::generate_only_additions(content_string));
  157. return;
  158. }
  159. const auto& original_content = m_git_repo->original_file_content(file_path);
  160. const auto& diff = m_git_repo->unstaged_diff(file_path);
  161. ASSERT(original_content.has_value() && diff.has_value());
  162. m_view_diff_callback(original_content.value(), diff.value());
  163. }
  164. }