web_git 0.0.2.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,114 @@
1
+ module WebGit
2
+ require "git"
3
+ require "ansispan"
4
+
5
+ class Graph
6
+ require "action_view"
7
+ require "action_view/helpers"
8
+ include ActionView::Helpers::DateHelper
9
+ attr_accessor :heads
10
+
11
+ def initialize(git)
12
+ @git = git
13
+ @full_list = []
14
+ @heads = {}
15
+ end
16
+
17
+ def to_hash
18
+
19
+ has_changes = has_untracked_changes?
20
+ if has_changes
21
+ temporarily_stash_changes
22
+ end
23
+
24
+ draw_graph
25
+
26
+ if has_changes
27
+ stash_pop
28
+ end
29
+ @full_list
30
+ end
31
+
32
+ def self.project_root
33
+ if defined?(Rails) && Rails.respond_to?("root")
34
+ return Rails.root
35
+ end
36
+
37
+ if defined?(Bundler)
38
+ return Bundler.root
39
+ end
40
+
41
+ Dir.pwd
42
+ end
43
+
44
+ def cli_graph
45
+ Dir.chdir(Graph.project_root) do
46
+ @cli_graph = `git log --oneline --decorate --graph --all --color`
47
+ all_commits = `git log --all --format=format:%H`.split("\n").map{|a| a.slice(0,7)}
48
+
49
+ @cli_graph = Ansispan.convert(@cli_graph)
50
+ all_commits.each do |sha|
51
+ sha_button = "<span class=\"commit\"><button class=\"btn btn-link sha\">#{sha}</button></span>"
52
+ @cli_graph.gsub!(sha, sha_button)
53
+ end
54
+ end
55
+ @cli_graph
56
+ end
57
+
58
+ def has_untracked_changes?
59
+ @git.diff.size > 0
60
+ end
61
+
62
+ def temporarily_stash_changes
63
+ @git.add(all: true)
64
+ stash_count = Git::Stashes.new(@git).count
65
+ Git::Stash.new(@git, "Temporary Stash #{stash_count}")
66
+ end
67
+
68
+ def stash_pop
69
+ stashes = Git::Stashes.new(@git)
70
+ stashes.apply(0)
71
+ end
72
+
73
+ def draw_graph
74
+ starting_branch = @git.current_branch
75
+ branches = @git.branches.local.map(&:name)
76
+ branches.each do |branch_name|
77
+ branch = { branch: branch_name }
78
+ @git.checkout(branch_name)
79
+ log_commits = build_array_of_commit_hashes
80
+ branch[:log] = log_commits
81
+ branch[:head] = log_commits.last[:sha]
82
+ @full_list.push branch
83
+ end
84
+ @git.checkout(starting_branch)
85
+
86
+ @full_list.each do |branch_hash|
87
+ head_sha = branch_hash[:head]
88
+ branch_name = branch_hash[:branch]
89
+
90
+ if @heads[head_sha].nil?
91
+ @heads[head_sha] = [branch_name]
92
+ else
93
+ @heads[head_sha].push branch_name
94
+ end
95
+ end
96
+
97
+ end
98
+
99
+ def build_array_of_commit_hashes
100
+ log_commits = []
101
+ @git.log.sort_by(&:date).each do |git_commit_object|
102
+ commit = {}
103
+ commit[:sha] = git_commit_object.sha.slice(0..7)
104
+ commit[:date] = git_commit_object.date
105
+ commit[:formatted_date] = time_ago_in_words(git_commit_object.date)
106
+ commit[:message] = git_commit_object.message
107
+ commit[:author] = git_commit_object.author.name
108
+ commit[:heads] = []
109
+ log_commits.push commit
110
+ end
111
+ log_commits
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,34 @@
1
+ require 'timeout'
2
+ require 'web_git/exceptions'
3
+
4
+ module WebGit
5
+ class Heroku
6
+ def self.authenticate(email, password)
7
+ raise ArgumentError.new("Email and password cannot be blank.") if email.blank? || password.blank?
8
+ script = File.join( File.dirname(__FILE__), '/../scripts/heroku_login.exp')
9
+
10
+ command = "#{script} #{email} #{password}"
11
+ rout, wout = IO.pipe
12
+ pid = Process.spawn(command, :out => wout)
13
+
14
+ begin
15
+ status = Timeout.timeout(30) do
16
+ _, status = Process.wait2(pid)
17
+ wout.close
18
+ end
19
+ stdout = rout.readlines.join("\n")
20
+ rout.close
21
+ message = stdout.match(/Error.*\./).to_s
22
+ raise WebGit::AuthenticationError.new(message) if stdout.include?("Error")
23
+
24
+ rescue Timeout::Error
25
+ Process.kill('TERM', script_pid)
26
+ raise Timeout::Error.new("Sign in took longer than 30 seconds.")
27
+ end
28
+ end
29
+
30
+ def self.whoami
31
+ `heroku whoami`.chomp
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,8 @@
1
+ # required for git
2
+ class String
3
+ def strip_heredoc
4
+ indent = scan(/^[ \t]*(?=\S)/).min.size || 0
5
+ gsub(/^[ \t]{#{indent}}/, '')
6
+ end
7
+ end
8
+
data/lib/web_git.rb CHANGED
@@ -1,10 +1,207 @@
1
- require "web_git/engine"
2
- require "jquery-rails"
3
- require "octicons_helper"
4
- require "tether-rails"
5
- require "turbolinks"
6
- require "git_clone_url"
1
+
2
+ require "web_git/version"
7
3
 
8
4
  module WebGit
9
- # Your code goes here...
5
+ require "active_support"
6
+ require "web_git/diff"
7
+ require "web_git/graph"
8
+ require "web_git/heroku"
9
+ require "web_git/string"
10
+ require "sinatra"
11
+ require "date"
12
+ require "git"
13
+ class Server < Sinatra::Base
14
+ enable :sessions
15
+
16
+ get '/log' do
17
+ graph = WebGit::Graph.new(git)
18
+ graph.to_hash.to_json
19
+ #sha = commit.sha.slice(0..7)
20
+ # commit_date = Date.parse commit.date
21
+ # strftime("%a, %d %b %Y, %H:%M %z") -> time_ago_in_words(commit_date)
22
+ # * 76eff73 - Wed, 11 Mar 2020 19:58:21 +0000 (13 days ago) (HEAD -> current_branch)
23
+ # | blease - Jelani Woods
24
+
25
+ # " * " + sha + " - " + commit_date + " (" + time_ago_in_words(commit_date) + ") " + "\n\t| " + commit.message
26
+ end
27
+
28
+ get "/" do
29
+ initialize_flash
30
+ clear_flash
31
+ # Update git index
32
+ git.status.changed.each do
33
+ git.diff.entries
34
+ end
35
+ status = git.status
36
+ # Just need the file names
37
+ @changed_files = status.changed.keys
38
+ @deleted_files = status.added.keys
39
+ @untracked_files = status.untracked.keys
40
+ @added_files = status.deleted.keys
41
+
42
+ @statuses = [
43
+ { name: "Changed Files:", file_list: @changed_files },
44
+ { name: "Untracked Files:", file_list: @untracked_files },
45
+ { name: "Deleted Files:", file_list: @deleted_files },
46
+ { name: "Added Files:", file_list: @added_files }
47
+ ]
48
+
49
+ @current_branch = git.current_branch
50
+ # g.branch(@current_branch).checkout # maybe?
51
+ # TODO use git gem for status
52
+ @status = `git status`
53
+ @diff = git.diff
54
+ @diff = Diff.diff_to_html(git.diff.to_s)
55
+ if git.log.count > 1
56
+ last_diff = git.diff("HEAD~1", "HEAD").to_s + "\n"
57
+ @last_diff_html = Diff.last_to_html(last_diff)
58
+ end
59
+ @last_diff_html = ""
60
+ @branches = git.branches.local.map(&:full)
61
+
62
+ logs = git.log
63
+ @last_commit_message = logs.first.message
64
+ @head = git.show.split("\n").first.split[1].slice(0..7)
65
+ @list = []
66
+ # (HEAD -> jw-non-sweet)
67
+ # TODO show where branches are on different remotes
68
+ @remotes = git.remotes.map {|remote| "#{remote.name}: #{remote.url}" }
69
+ # (origin/master, origin/jw-non-sweet, origin/HEAD)
70
+ # git.branches[:master].gcommit
71
+
72
+ graph = WebGit::Graph.new(git)
73
+ @graph_hash = graph.to_hash
74
+ @cli_graph_interactive = graph.cli_graph
75
+ @graph_branches = @graph_hash.sort do |branch_a, branch_b|
76
+ branch_b[:log].last[:date] <=> branch_a[:log].last[:date]
77
+ end
78
+
79
+ # TODO heroku stuff
80
+ @heroku_auth = WebGit::Heroku.whoami
81
+ erb :status
82
+ end
83
+
84
+ post "/commit" do
85
+ title = params[:title]
86
+ description = params[:description]
87
+
88
+ # TODO validate commit message
89
+ if title.nil? || title.gsub(" ", "").length == 0
90
+ session[:alert] = "You need to make a commit message."
91
+ redirect to("/")
92
+ end
93
+
94
+ unless description.nil?
95
+ title += "\n#{description}"
96
+ end
97
+
98
+ safe_git_action(:commit, args: title, notice: "Commit created successfully", alert: "Failed to create commit")
99
+ redirect to("/")
100
+ end
101
+
102
+ get "/stash" do
103
+ safe_git_action(:stash, notice: "Changes stashed.", alert: "Failed to stash changes")
104
+ redirect to("/")
105
+ end
106
+
107
+ post "/branch/checkout/new" do
108
+ # TODO validate branch name
109
+ name = params.fetch(:branch_name).downcase.gsub(" ", "_")
110
+ commit = params.fetch(:commit_hash)
111
+
112
+ safe_git_action(:checkout, args: name, notice: "Branch #{name}, created successfully.", alert: "Failed to create branch")
113
+ safe_git_action(:reset_hard, args: commit, alert: "Failed to checkout branch at #{commit}")
114
+ redirect to("/")
115
+ end
116
+
117
+ post "/branch/checkout" do
118
+ name = params.fetch(:branch_name).downcase.gsub(" ", "_")
119
+
120
+ safe_git_action(:checkout, args: name, notice: "Switched to branch: #{name} successfully.", alert: "Failed to switch branch")
121
+ redirect to("/")
122
+ end
123
+
124
+ # TODO make delete request somehow with the links
125
+ post "/branch/delete" do
126
+ name = params.fetch(:branch_name).downcase.gsub(" ", "_")
127
+
128
+ safe_git_action(:delete, args: name, notice: "Deleted branch: #{name} successfully.", alert: "Failed to delete branch")
129
+ redirect to("/")
130
+ end
131
+
132
+ post "/push" do
133
+ # TODO push to heroku eventually, multiple remotes
134
+
135
+ safe_git_action(:push, notice: "Pushed to GitHub successfully.", alert: "Failed to push")
136
+ redirect to("/")
137
+ end
138
+
139
+ post "/pull" do
140
+ safe_git_action(:pull, notice: "Pulled successfully.", alert: "Git Pull failed")
141
+ redirect to("/")
142
+ end
143
+
144
+ post "/heroku/login" do
145
+ email = params[:heroku_email]
146
+ password = params[:heroku_password]
147
+
148
+ begin
149
+ WebGit::Heroku.authenticate(email, password)
150
+ set_flash(:notice, "Successfully logged into Heroku.")
151
+ rescue => exception
152
+ set_flash(:alert, "There was a problem logging into Heroku. #{exception.message}")
153
+ end
154
+ redirect to("/")
155
+ end
156
+
157
+ protected
158
+
159
+ def git
160
+ working_dir = File.exist?(Dir.pwd + "/.git") ? Dir.pwd : Dir.pwd + "/.."
161
+ @git ||= Git.open(working_dir)
162
+ end
163
+
164
+ def safe_git_action(method, **options)
165
+ begin
166
+ case method
167
+ when :push
168
+ git.push('origin', git.current_branch)
169
+ when :pull
170
+ git.pull
171
+ when :stash
172
+ git.add(all: true)
173
+ stash_count = Git::Stashes.new(git).count
174
+ Git::Stash.new(git, "Stash #{stash_count}")
175
+ when :commit
176
+ git.add(all: true)
177
+ git.commit(options[:args])
178
+ when :checkout
179
+ git.branch(options[:args]).checkout
180
+ when :reset_hard
181
+ commit = git.gcommit(options[:args])
182
+ git.reset_hard(commit)
183
+ when :delete
184
+ git.branch(options[:args]).delete
185
+ end
186
+ set_flash(:notice, options[:notice])
187
+ rescue Git::GitExecuteError => exception
188
+ alert_message = "#{options[:alert]}: #{exception.message.split("\n").last}"
189
+ set_flash(:alert, alert_message)
190
+ end
191
+ end
192
+
193
+ def set_flash(name, message)
194
+ session[name] = message
195
+ end
196
+
197
+ def clear_flash
198
+ session[:alert] = nil
199
+ session[:notice] = nil
200
+ end
201
+
202
+ def initialize_flash
203
+ @alert = session[:alert]
204
+ @notice = session[:notice]
205
+ end
206
+ end
10
207
  end
data/web_git.gemspec CHANGED
@@ -2,18 +2,18 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Juwelier::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: web_git 0.0.3 ruby lib
5
+ # stub: web_git 0.1.0 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "web_git".freeze
9
- s.version = "0.0.3"
9
+ s.version = "0.1.0"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib".freeze]
13
- s.authors = ["Raghu Betina".freeze]
14
- s.date = "2019-07-11"
15
- s.description = "WebGit is a Rails Engine that provides an in-browser visual interface to a simple but effective Git workflow. For educational purposes.".freeze
16
- s.email = "raghu@firstdraft.com".freeze
13
+ s.authors = ["Raghu Betina".freeze, "Jelani Woods".freeze]
14
+ s.date = "2022-03-30"
15
+ s.description = "WebGit is an embeddable Sinatra app that provides an in-browser visual interface to a simple but effective Git workflow. For educational purposes.".freeze
16
+ s.email = ["raghu@firstdraft.com".freeze, "jelani@firstdraft.com".freeze]
17
17
  s.extra_rdoc_files = [
18
18
  "LICENSE.txt",
19
19
  "README.markdown"
@@ -28,88 +28,47 @@ Gem::Specification.new do |s|
28
28
  "README.markdown",
29
29
  "Rakefile",
30
30
  "VERSION",
31
- "ansi2html.sh",
32
- "app/assets/javascripts/web_git/application.js",
33
- "app/assets/javascripts/web_git/bootstrap.min.js",
34
- "app/assets/javascripts/web_git/popper.min.js",
35
- "app/assets/stylesheets/web_git/application.scss",
36
- "app/assets/stylesheets/web_git/bootstrap.min.css",
37
- "app/assets/stylesheets/web_git/font-awesome.min.css",
38
- "app/assets/stylesheets/web_git/octicons.css",
39
- "app/controllers/web_git/application_controller.rb",
40
- "app/controllers/web_git/branches_controller.rb",
41
- "app/controllers/web_git/commands_controller.rb",
42
- "app/controllers/web_git/commits_controller.rb",
43
- "app/views/layouts/web_git/application.html.erb",
44
- "app/views/web_git/commands/hello.html.erb",
45
- "app/views/web_git/commands/status.html.erb",
46
- "config/routes.rb",
31
+ "lib/generators/web_git/install_generator.rb",
32
+ "lib/scripts/heroku_login.exp",
33
+ "lib/views/status.erb",
47
34
  "lib/web_git.rb",
48
- "lib/web_git/engine.rb",
35
+ "lib/web_git/diff.rb",
36
+ "lib/web_git/exceptions.rb",
37
+ "lib/web_git/graph.rb",
38
+ "lib/web_git/heroku.rb",
39
+ "lib/web_git/string.rb",
49
40
  "lib/web_git/version.rb",
50
- "web_git-0.0.2.gem",
51
41
  "web_git.gemspec"
52
42
  ]
53
43
  s.homepage = "http://github.com/firstdraft/web_git".freeze
54
44
  s.licenses = ["MIT".freeze]
55
- s.rubygems_version = "2.7.8".freeze
45
+ s.rubygems_version = "3.1.6".freeze
56
46
  s.summary = "An in-browser Git GUI for your Rails project".freeze
57
47
 
58
48
  if s.respond_to? :specification_version then
59
49
  s.specification_version = 4
50
+ end
60
51
 
61
- if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
62
- s.add_runtime_dependency(%q<tzinfo-data>.freeze, [">= 0"])
63
- s.add_runtime_dependency(%q<tether-rails>.freeze, [">= 0"])
64
- s.add_runtime_dependency(%q<octicons_helper>.freeze, [">= 0"])
65
- s.add_runtime_dependency(%q<turbolinks>.freeze, ["~> 5"])
66
- s.add_runtime_dependency(%q<jquery-rails>.freeze, [">= 0"])
67
- s.add_runtime_dependency(%q<git_clone_url>.freeze, [">= 0"])
68
- s.add_development_dependency(%q<rspec>.freeze, ["~> 3.5.0"])
69
- s.add_development_dependency(%q<rdoc>.freeze, ["~> 3.12"])
70
- s.add_development_dependency(%q<juwelier>.freeze, ["~> 2.1.0"])
71
- s.add_development_dependency(%q<simplecov>.freeze, [">= 0"])
72
- s.add_development_dependency(%q<pry>.freeze, ["~> 0"])
73
- s.add_development_dependency(%q<pry-byebug>.freeze, ["~> 3"])
74
- s.add_development_dependency(%q<pry-doc>.freeze, ["~> 0"])
75
- s.add_development_dependency(%q<pry-remote>.freeze, ["~> 0"])
76
- s.add_development_dependency(%q<pry-rescue>.freeze, ["~> 1"])
77
- s.add_development_dependency(%q<pry-stack_explorer>.freeze, ["~> 0"])
78
- else
79
- s.add_dependency(%q<tzinfo-data>.freeze, [">= 0"])
80
- s.add_dependency(%q<tether-rails>.freeze, [">= 0"])
81
- s.add_dependency(%q<octicons_helper>.freeze, [">= 0"])
82
- s.add_dependency(%q<turbolinks>.freeze, ["~> 5"])
83
- s.add_dependency(%q<jquery-rails>.freeze, [">= 0"])
84
- s.add_dependency(%q<git_clone_url>.freeze, [">= 0"])
85
- s.add_dependency(%q<rspec>.freeze, ["~> 3.5.0"])
86
- s.add_dependency(%q<rdoc>.freeze, ["~> 3.12"])
87
- s.add_dependency(%q<juwelier>.freeze, ["~> 2.1.0"])
88
- s.add_dependency(%q<simplecov>.freeze, [">= 0"])
89
- s.add_dependency(%q<pry>.freeze, ["~> 0"])
90
- s.add_dependency(%q<pry-byebug>.freeze, ["~> 3"])
91
- s.add_dependency(%q<pry-doc>.freeze, ["~> 0"])
92
- s.add_dependency(%q<pry-remote>.freeze, ["~> 0"])
93
- s.add_dependency(%q<pry-rescue>.freeze, ["~> 1"])
94
- s.add_dependency(%q<pry-stack_explorer>.freeze, ["~> 0"])
95
- end
52
+ if s.respond_to? :add_runtime_dependency then
53
+ s.add_runtime_dependency(%q<actionview>.freeze, [">= 0"])
54
+ s.add_runtime_dependency(%q<ansispan>.freeze, [">= 0"])
55
+ s.add_runtime_dependency(%q<diffy>.freeze, [">= 0"])
56
+ s.add_runtime_dependency(%q<git>.freeze, [">= 0"])
57
+ s.add_runtime_dependency(%q<sinatra>.freeze, [">= 0"])
58
+ s.add_runtime_dependency(%q<tzinfo-data>.freeze, [">= 0"])
59
+ s.add_development_dependency(%q<juwelier>.freeze, ["~> 2.1.0"])
60
+ s.add_development_dependency(%q<rdoc>.freeze, [">= 6.3.1"])
61
+ s.add_development_dependency(%q<rspec>.freeze, ["~> 3.5.0"])
96
62
  else
63
+ s.add_dependency(%q<actionview>.freeze, [">= 0"])
64
+ s.add_dependency(%q<ansispan>.freeze, [">= 0"])
65
+ s.add_dependency(%q<diffy>.freeze, [">= 0"])
66
+ s.add_dependency(%q<git>.freeze, [">= 0"])
67
+ s.add_dependency(%q<sinatra>.freeze, [">= 0"])
97
68
  s.add_dependency(%q<tzinfo-data>.freeze, [">= 0"])
98
- s.add_dependency(%q<tether-rails>.freeze, [">= 0"])
99
- s.add_dependency(%q<octicons_helper>.freeze, [">= 0"])
100
- s.add_dependency(%q<turbolinks>.freeze, ["~> 5"])
101
- s.add_dependency(%q<jquery-rails>.freeze, [">= 0"])
102
- s.add_dependency(%q<git_clone_url>.freeze, [">= 0"])
103
- s.add_dependency(%q<rspec>.freeze, ["~> 3.5.0"])
104
- s.add_dependency(%q<rdoc>.freeze, ["~> 3.12"])
105
69
  s.add_dependency(%q<juwelier>.freeze, ["~> 2.1.0"])
106
- s.add_dependency(%q<simplecov>.freeze, [">= 0"])
107
- s.add_dependency(%q<pry>.freeze, ["~> 0"])
108
- s.add_dependency(%q<pry-byebug>.freeze, ["~> 3"])
109
- s.add_dependency(%q<pry-doc>.freeze, ["~> 0"])
110
- s.add_dependency(%q<pry-remote>.freeze, ["~> 0"])
111
- s.add_dependency(%q<pry-rescue>.freeze, ["~> 1"])
112
- s.add_dependency(%q<pry-stack_explorer>.freeze, ["~> 0"])
70
+ s.add_dependency(%q<rdoc>.freeze, [">= 6.3.1"])
71
+ s.add_dependency(%q<rspec>.freeze, ["~> 3.5.0"])
113
72
  end
114
73
  end
115
74