ezgit 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,135 @@
1
+ ## EZGit
2
+ =====
3
+
4
+ (This project is currently under development)
5
+
6
+ * http://github.com/ajjames/ezgit
7
+ * http://rubygems.org/gems/ezgit
8
+
9
+
10
+ ## DESCRIPTION
11
+
12
+ EZGit is a command-line interface based on Ruby. The **goal** of **EZGit** is to simplify the daily git tasks used in a multi-team enterprise development environment without the need to fully understand the full complex beauty of Git. With EZGit, committers with any level of Git knowledge can work in the repo with confidence and consistency.
13
+
14
+ EZGit abstracts away many Git concepts that are consistent sources of confusion. Concepts such as...
15
+ * _**Where do branches live? On my local machine or on the remote?**_ Who cares? EZGit handles it for you.
16
+ * _**What's the difference between the working directory, the stage(or index), and the local repo?**_ I could spend hours explaining it to you, but if you don't really want to know, EZGit is here to make that stuff go away.
17
+
18
+ EZGit replaces git's complex and overloaded commands with simple, intentional, ruby-styled names.
19
+
20
+ In future updates, EZGit will implement a branching strategy that has been honed and refined over the last two years by a large agile enterprise development shop. The concepts of merging (and rebasing) will be equally simplified so that you can concentrate on your code, and not on, "how the heck do I merge these changes?!"
21
+
22
+ Best of all, EZGit is still Git. So if you are a Git-Fu master, you can go back to using your favorite, obtuse Git commands at any time. They'll always be there waiting for you when you need to do the complex stuff.
23
+
24
+ If you try EZGit and find that it doesn't quite work for you, drop me a line and let me know why. I'm happy to look at other use cases and consider including them in future updates.
25
+
26
+ Cheers,
27
+
28
+ AJ
29
+
30
+
31
+ ## INSTALLATION
32
+
33
+ To intall:
34
+
35
+ gem install ezgit
36
+
37
+ EZGit changes a lot (especially during this development phase). To update your existing installs, you can use `gem update ezgit`, or you can use EZGit's self-update command:
38
+
39
+ ez update
40
+
41
+
42
+ ## HELP TEXT
43
+
44
+ Once installed, take a look at the help menu:
45
+
46
+ ez -h
47
+
48
+
49
+ EZGit is a simple interface for working with git repositories.
50
+
51
+ Usage:
52
+ ez [<options>] <commands>
53
+
54
+ commands are:
55
+ clean! Wipes all untracked and ignored files.
56
+ clean Wipes all untracked files, but allows ignored files to remain.
57
+ clone Creates a copy of a repository.
58
+ commit Creates a commit for all current file changes.
59
+ create Create a new branch in the current state.
60
+ delete! Completely delete a given branch.
61
+ goto! Move to the location in the tree specified by a commit id. All changes will be lost.
62
+ info Shows files status and current branches.
63
+ move Switch and move changes to the given branch.
64
+ pull Pulls the latest changes from the remote.
65
+ push Pushes the current branch changes to the remote.
66
+ reset! Deletes changes in all tracked files. Untracked files will not be affected.
67
+ switch! Abandon all changes and switch to the given branch.
68
+ switch Switch to the given branch if there are not changes.
69
+ tree Shows git tree history for the current branch
70
+ update Attempts to self-update from rubygems.org.
71
+
72
+ options are:
73
+ --dry-run-flag, -n: Forces all commands to be passive.
74
+ --debug, -d: Shows command level debug info.
75
+ --version, -v: Print version and exit
76
+ --help, -h: Show this message
77
+
78
+
79
+ ## EXAMPLE
80
+
81
+ ```
82
+ ~/git/test_repo > git init
83
+ Initialized empty Git repository in /Users/AJ/git/test_repo/.git/
84
+
85
+ ~/git/test_repo > touch file1 file2 file3 .gitignore
86
+
87
+ ~/git/test_repo > ez info
88
+ ________________________________
89
+
90
+ REPOSITORY TREE(last 5 commits)
91
+ There is no history yet.
92
+
93
+ BRANCHES:
94
+
95
+ TO BE COMMITTED ON: master
96
+ Add .gitignore
97
+ Add file1
98
+ Add file2
99
+ Add file3
100
+
101
+ SYNC STATUS:
102
+ Your master branch does not yet exist on the remote.
103
+ (Use 'ez pull' to update the remote.)
104
+ ________________________________
105
+
106
+ ~/git/test_repo > ez commit "initial commit"
107
+
108
+ [master (root-commit) e315617] initial commit
109
+ 0 files changed
110
+ create mode 100644 .gitignore
111
+ create mode 100644 file1
112
+ create mode 100644 file2
113
+ create mode 100644 file3
114
+
115
+ REPOSITORY TREE(last 5 commits)
116
+ * e315617 (0 seconds ago) AJ James initial commit (HEAD, master)
117
+
118
+ TO BE COMMITTED ON: master
119
+ No changes.
120
+
121
+ SYNC STATUS:
122
+ Your master branch does not yet exist on the remote.
123
+ (Use 'ez pull' to update the remote.)
124
+ ```
125
+
126
+
127
+ ## ISSUES
128
+
129
+ To report issues at:
130
+ https://github.com/ajjames/ezgit/issues
131
+
132
+
133
+ ## ACKNOWLEDGEMENTS
134
+
135
+ This project makes use of Trollop (Copyright 2007 William Morgan). An excellent light-weight command-line parser.
data/bin/ez ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift(File.dirname(__FILE__) + '/../lib') unless $:.include?(File.dirname(__FILE__) + '/../lib')
3
+ require 'ezgit'
data/bin/ez.bat ADDED
@@ -0,0 +1,2 @@
1
+ @ECHO OFF
2
+ ruby %~dp0ez %* | %~dp0\wac
data/bin/wac.exe ADDED
Binary file
data/lib/ezgit.rb ADDED
@@ -0,0 +1,5 @@
1
+ require "ezgit/version"
2
+ require "ezgit/run"
3
+
4
+ module Ezgit
5
+ end
@@ -0,0 +1,71 @@
1
+ require 'ezgit/git'
2
+
3
+ class Commands
4
+
5
+ attr_accessor :all, :symbols, :names, :help_list, :options, :git
6
+
7
+
8
+ def initialize
9
+ @all = []
10
+ @symbols = []
11
+ @names = []
12
+ @help_list = ''
13
+ @options = {}
14
+ end
15
+
16
+
17
+ def read
18
+ commands_dir = File.expand_path "commands", File.dirname(__FILE__)
19
+ files = Dir["#{commands_dir}/*.rb"]
20
+ files.each do |command_file|
21
+ require command_file
22
+ current_symbol = @all.last[:name]
23
+ @symbols << current_symbol
24
+ @names << current_symbol.to_s
25
+ @help_list << "\t#{@names.last.cyan.bold}\t#{@all.last[:help].to_s.bold}\n"
26
+ end
27
+ end
28
+
29
+
30
+ def process
31
+ @git = Git.new(@options)
32
+ @cmd = ARGV.shift
33
+ matched = false
34
+ @all.each do |current|
35
+ if current[:name].to_s.eql? @cmd
36
+ matched = true
37
+ @cmd_opts = Trollop::options do
38
+ usage = current[:usage]
39
+ usage ||= "ez #{current[:name].to_s} [<options>]"
40
+ banner <<-HELP_DESCRIPTION
41
+ command: ez #{current[:name].to_s}\n
42
+ #{current[:help].to_s}
43
+
44
+ Usage:
45
+ #{usage}
46
+
47
+ options are:
48
+ HELP_DESCRIPTION
49
+
50
+ current[:options].each do |cmd_opt|
51
+ sym = cmd_opt[0]
52
+ info = cmd_opt[1]
53
+ flags = cmd_opt[2]
54
+ opt sym, info, flags
55
+ end
56
+ end
57
+ current[:action].call(@cmd_opts, ARGV)
58
+ break
59
+ end
60
+ end
61
+ Trollop::die "unknown subcommand #{@cmd.inspect}" if not matched
62
+ if $commands.options[:debug]
63
+ puts "Global options: #{$commands.options.inspect}"
64
+ puts "Subcommand: #{@cmd.inspect}"
65
+ puts "Subcommand options: #{@cmd_opts.inspect}"
66
+ puts "Remaining arguments: #{ARGV.inspect}"
67
+ end
68
+ end
69
+
70
+
71
+ end
@@ -0,0 +1,10 @@
1
+ $commands.all << {
2
+ name: 'clean!',
3
+ help: 'Wipes all untracked and ignored files.',
4
+ options: [
5
+ [:force, 'Automatically approve and bypass the confirmation prompt.', short: '-f']
6
+ ],
7
+ action: lambda do |opts, args|
8
+ $commands.git.clean!(true, opts)
9
+ end
10
+ }
@@ -0,0 +1,10 @@
1
+ $commands.all << {
2
+ name: 'clean',
3
+ help: 'Wipes all untracked files, but allows ignored files to remain.',
4
+ options: [
5
+ [:force, 'Automatically approve and bypass the confirmation prompt.', short: '-f']
6
+ ],
7
+ action: lambda do |opts, args|
8
+ $commands.git.clean!(false, opts)
9
+ end
10
+ }
@@ -0,0 +1,9 @@
1
+ $commands.all << {
2
+ name: 'clone',
3
+ help: 'Creates a copy of a repository.',
4
+ usage: 'ez clone <source_url> [<destination_directory>]',
5
+ options: [],
6
+ action: lambda do |opts, args|
7
+ $commands.git.clone(args)
8
+ end
9
+ }
@@ -0,0 +1,9 @@
1
+ $commands.all << {
2
+ name: 'commit',
3
+ help: 'Creates a commit for all current file changes.',
4
+ usage: 'ez commit "message"',
5
+ options: [],
6
+ action: lambda do |opts, args|
7
+ $commands.git.commit(args)
8
+ end
9
+ }
@@ -0,0 +1,9 @@
1
+ $commands.all << {
2
+ name: 'create',
3
+ help: 'Create a new branch in the current state.',
4
+ usage: 'ez create <branch_name>',
5
+ options: [],
6
+ action: lambda do |opts, args|
7
+ $commands.git.create(opts,args)
8
+ end
9
+ }
@@ -0,0 +1,11 @@
1
+ $commands.all << {
2
+ name: 'delete!',
3
+ help: 'Completely delete a given branch.',
4
+ usage: 'ez delete! <branch_name>',
5
+ options: [
6
+ [:force, 'Automatically approve and bypass the confirmation prompt.', short: '-f']
7
+ ],
8
+ action: lambda do |opts, args|
9
+ $commands.git.delete!(opts,args)
10
+ end
11
+ }
@@ -0,0 +1,11 @@
1
+ $commands.all << {
2
+ name: 'goto!',
3
+ help: 'Move to the location in the tree specified by a commit id. All changes will be lost.',
4
+ usage: 'ez goto! <commit>',
5
+ options: [
6
+ [:force, 'Automatically approve and bypass the confirmation prompt.', short: '-f']
7
+ ],
8
+ action: lambda do |opts, args|
9
+ $commands.git.goto!(opts, args)
10
+ end
11
+ }
@@ -0,0 +1,10 @@
1
+ $commands.all << {
2
+ name: 'info',
3
+ help: 'Shows files status and current branches.',
4
+ options: [
5
+ [:ignored, 'Lists the files are being ignored.', short: '-i']
6
+ ],
7
+ action: lambda do |opts, args|
8
+ $commands.git.info(opts)
9
+ end
10
+ }
@@ -0,0 +1,10 @@
1
+ $commands.all << {
2
+ name: 'move',
3
+ help: 'Switch and move changes to the given branch.',
4
+ usage: 'ez move <branch_name>',
5
+ options: [],
6
+ action: lambda do |opts, args|
7
+ opts[:move] = true
8
+ $commands.git.switch!(opts, args)
9
+ end
10
+ }
@@ -0,0 +1,8 @@
1
+ $commands.all << {
2
+ name: 'pull',
3
+ help: 'Pulls the latest changes from the remote.',
4
+ options: [],
5
+ action: lambda do |opts, args|
6
+ $commands.git.pull
7
+ end
8
+ }
@@ -0,0 +1,8 @@
1
+ $commands.all << {
2
+ name: 'push',
3
+ help: 'Pushes the current branch changes to the remote.',
4
+ options: [],
5
+ action: lambda do |opts, args|
6
+ $commands.git.push
7
+ end
8
+ }
@@ -0,0 +1,10 @@
1
+ $commands.all << {
2
+ name: 'reset!',
3
+ help: 'Deletes changes in all tracked files. Untracked files will not be affected.',
4
+ options: [
5
+ [:force, 'Automatically approve and bypass the confirmation prompt.', short: '-f']
6
+ ],
7
+ action: lambda do |opts, args|
8
+ $commands.git.reset_hard!(opts)
9
+ end
10
+ }
@@ -0,0 +1,12 @@
1
+ $commands.all << {
2
+ name: 'switch!',
3
+ help: 'Abandon all changes and switch to the given branch.',
4
+ usage: 'ez switch! <branch_name>',
5
+ options: [
6
+ [:force, 'Automatically approve and bypass the confirmation prompt.', short: '-f']
7
+ ],
8
+ action: lambda do |opts, args|
9
+ opts[:switch!] = true
10
+ $commands.git.switch!(opts, args)
11
+ end
12
+ }
@@ -0,0 +1,10 @@
1
+ $commands.all << {
2
+ name: 'switch',
3
+ help: 'Switch to the given branch if there are not changes.',
4
+ usage: 'ez switch <branch_name>',
5
+ options: [],
6
+ action: lambda do |opts, args|
7
+ opts[:switch] = true
8
+ $commands.git.switch!(opts,args)
9
+ end
10
+ }
@@ -0,0 +1,13 @@
1
+ $commands.all << {
2
+ name: 'tree',
3
+ help: 'Shows git tree history for the current branch',
4
+ usage: 'ez tree',
5
+ options: [
6
+ [:current, 'Shows only the current branch. All other branches are filtered out.', short: '-c'],
7
+ [:number, 'Number of entries to display.', short: '-n', default:20]
8
+ ],
9
+ action: lambda do |opts, args|
10
+ count = args[0] || 20
11
+ $commands.git.display_log_graph(opts[:number], !opts[:current])
12
+ end
13
+ }
@@ -0,0 +1,9 @@
1
+ $commands.all << {
2
+ name: 'update',
3
+ help: 'Attempts to self-update from rubygems.org.',
4
+ options: [],
5
+ action: lambda do |opts, args|
6
+ system('gem install ezgit; gem cleanup --verbose ezgit')
7
+ exit
8
+ end
9
+ }
@@ -0,0 +1,22 @@
1
+ require 'ezgit/version'
2
+
3
+ class EzCommand
4
+ def self.options(subcommand_names, subcommand_help_list)
5
+ return Trollop::options do
6
+ version Ezgit::VERSION
7
+ banner <<-HELP_DESCRIPTION
8
+ EZGit is a simple interface for working with git repositories.
9
+
10
+ Usage:
11
+ ez [<options>] <commands>
12
+
13
+ commands are:
14
+ #{subcommand_help_list}
15
+ options are:
16
+ HELP_DESCRIPTION
17
+ opt :dry_run_flag, 'Forces all commands to be passive.', short: '-n'
18
+ opt :debug, 'Shows command level debug info.', short: '-d'
19
+ stop_on subcommand_names
20
+ end
21
+ end
22
+ end
data/lib/ezgit/git.rb ADDED
@@ -0,0 +1,399 @@
1
+ require 'open3'
2
+
3
+ class Git
4
+
5
+ attr_reader :current_branch, :remote_branch, :all_branches, :all_uniq_branches
6
+ NO_BRANCH = '(nobranch)'
7
+
8
+
9
+ def initialize(global_options)
10
+ @its_a_dry_run = global_options[:dry_run_flag]
11
+ @dry_run_flag = @its_a_dry_run ? '-n' : ''
12
+ end
13
+
14
+
15
+ def current_branch
16
+ if @current_branch.nil?
17
+ remove_refs_regex = /.+\/(\w+)/
18
+ out = `git symbolic-ref HEAD 2>&1`.match(remove_refs_regex)
19
+ @current_branch = (out.nil?) ? NO_BRANCH : out[1].to_s
20
+ end
21
+ return @current_branch
22
+ end
23
+
24
+
25
+ def remote_branch(branch_name = nil)
26
+ if @remote_branch.nil?
27
+ @remote_branch = add_remote_to_branch(current_branch)
28
+ end
29
+ return @remote_branch
30
+ end
31
+
32
+
33
+ def add_remote_to_branch(branch_name)
34
+ # Get remote name. It may not be 'origin'
35
+ origin = `git remote show`.gsub(/\s/, '')
36
+ remote = "#{origin}/#{branch_name}"
37
+ remote = all_branches.include?(remote) ? remote : ''
38
+ return remote
39
+ end
40
+
41
+
42
+ def refresh_branches
43
+ @all_branches = nil
44
+ @all_uniq_branches = nil
45
+ end
46
+
47
+
48
+ def all_branches
49
+ if @all_branches.nil?
50
+ # create regexes to remove the refs, HEAD entry, spaces, *, etc
51
+ strip_head_regx = /^.*\/HEAD -> .*$\n/
52
+ strip_asterisks_and_spaces_regx = /[\* ]/
53
+ strip_remotes_regex = /remotes\//
54
+ git_a = `git branch -a --no-color`.gsub(strip_head_regx, '').gsub(strip_asterisks_and_spaces_regx, '').gsub(strip_remotes_regex, '')
55
+ @all_branches = git_a.split("\n")
56
+ end
57
+ return @all_branches
58
+ end
59
+
60
+
61
+ def all_uniq_branches
62
+ if @all_uniq_branches.nil?
63
+ remove_refs_regx = /([\* ])|(.*\/)/
64
+ @all_uniq_branches = []
65
+ all_branches.each do |br|
66
+ @all_uniq_branches << br.gsub(remove_refs_regx, '')
67
+ end
68
+ @all_uniq_branches.uniq!
69
+ @all_uniq_branches.sort!
70
+ end
71
+ return @all_uniq_branches
72
+ end
73
+
74
+
75
+ def display_log_graph(count = 5, show_all = false)
76
+ puts ''
77
+ puts "REPOSITORY TREE".white.bold + "(last #{count} commits)"
78
+ all = show_all ? '--all' : ''
79
+ stdin, stdout, stderr = Open3.popen3("git log --graph #{all} --format=format:\"#{CYAN}%h #{CLEAR + CYAN}(%cr) #{CYAN}%cn #{CLEAR + WHITE}%s#{CYAN + BOLD}%d#{CLEAR}\" --abbrev-commit --date=relative -n #{count}")
80
+ err = stderr.readlines
81
+ return puts 'There is no history yet.'.cyan.bold if err.any?
82
+ puts err.join('') + stdout.readlines.join('')
83
+ end
84
+
85
+
86
+ def display_branch_list_with_current
87
+ puts ''
88
+ puts ' BRANCHES:'.bold
89
+ brs = []
90
+ all_uniq_branches.each do |b|
91
+ #add an indicator if it is the current branch
92
+ b = b.eql?(current_branch) ? "#{b} <-- CURRENT".bold : b
93
+ # output the list
94
+ puts " #{b}".cyan
95
+ end
96
+ end
97
+
98
+
99
+ def info(opts=nil)
100
+ puts '________________________________'
101
+ $commands.git.display_log_graph
102
+ $commands.git.display_branch_list_with_current
103
+ $commands.git.display_current_changes(opts)
104
+ $commands.git.display_sync_status
105
+ puts '________________________________'
106
+ end
107
+
108
+
109
+ #returns :up_to_date/:no_remote/:rebase/:ahead/:behind, count
110
+ def check_remote_status
111
+ return :headless if current_branch == NO_BRANCH
112
+ ahead_count_rgx = /.*ahead.(\d+)/
113
+ behind_count_rgx = /.*behind.(\d+)/
114
+ stdin, stdout, stderr = Open3.popen3('git status -bs')
115
+ stat = stdout.readlines[0]
116
+ ahead_match = stat.match(ahead_count_rgx)
117
+ ahead_count = (ahead_match.nil?) ? '0' : ahead_match[1]
118
+ behind_match = stat.match(behind_count_rgx)
119
+ behind_count = (behind_match.nil?) ? '0' : behind_match[1]
120
+ case
121
+ when ahead_count > '0' && behind_count == '0'
122
+ return :ahead, ahead_count
123
+ when ahead_count == '0' && behind_count > '0'
124
+ return :behind, behind_count
125
+ when ahead_count > '0' && behind_count > '0'
126
+ return :rebase, '0'
127
+ else
128
+ return :no_remote, '0' if remote_branch.empty?
129
+ return :up_to_date, '0'
130
+ end
131
+ end
132
+
133
+
134
+ def display_sync_status
135
+ puts ''
136
+ puts ' SYNC STATUS:'.white.bold
137
+ stat, count = check_remote_status
138
+ commit_s = (count == 1) ? 'commit' : 'commits'
139
+ case stat
140
+ when :ahead
141
+ puts " Your #{current_branch.bold + CYAN} branch is ahead of the remote by #{count} #{commit_s}.".cyan
142
+ puts " (Use 'ez pull' to update the remote.)".cyan
143
+ when :behind
144
+ puts " Your #{current_branch.bold + YELLOW} branch is behind the remote by #{count} #{commit_s}.".yellow
145
+ puts " (Use 'ez pull' to get the new changes.)".yellow
146
+ when :rebase
147
+ puts " Your #{current_branch} branch has diverged #{count} #{commit_s} from the remote.".red.bold
148
+ puts " (Use must use git directly to put them back in sync.)".red.bold
149
+ when :no_remote
150
+ puts " Your #{current_branch.bold + CYAN} branch does not yet exist on the remote.".cyan
151
+ puts " (Use 'ez pull' to update the remote.)".cyan
152
+ when :headless
153
+ puts " You are in a headless state (not on a branch)".red.bold
154
+ puts " (Use 'ez create <branch>' to create a branch at this commit,".red.bold
155
+ puts " or use 'ez switch <branch>' to switch to a branch.)".red.bold
156
+ else
157
+ puts " Your #{current_branch.bold + GREEN} branch is in sync with the remote.".green
158
+ puts " (Use 'ez pull' to ensure it stays in sync.)".green
159
+ end
160
+ end
161
+
162
+
163
+ #returns (bool has_changes?, Array changes)
164
+ def check_local_changes(opts = nil)
165
+ ignored = (opts.nil? || opts[:ignored] == false) ? '' : '--ignored'
166
+ stdin, stdout, stderr = Open3.popen3("git status --untracked-files=all --porcelain #{ignored}")
167
+ changes = stdout.readlines
168
+ return changes.any?, changes
169
+ end
170
+
171
+
172
+ def display_current_changes(opts = nil)
173
+ puts ''
174
+ puts " TO BE COMMITTED ON: #{current_branch}".white.bold
175
+ has_changes, changes = check_local_changes(opts)
176
+ puts " No changes.".green unless has_changes
177
+ changes.collect! { |line|
178
+ line.sub!('!! ', CYAN + " ignore " + CLEAR)
179
+ line.gsub!(/ U |U /, RED + BOLD + " MERGE " + CLEAR)
180
+ line.gsub!(/ D |D /, RED + BOLD + " Delete " + CLEAR)
181
+ line.gsub!(/.R |R. /, YELLOW + BOLD + " Rename " + CLEAR)
182
+ line.gsub!(/A |\?\? /, YELLOW + BOLD + " Add " + CLEAR)
183
+ line.gsub!(/.M |M. /, YELLOW + BOLD + " Change " + CLEAR)
184
+ line
185
+ }
186
+ puts changes.sort!
187
+ end
188
+
189
+
190
+ def clone(args)
191
+ return puts 'invalid number of arguments. Requires a source. (Destination is optional.)' if args.count < 1 || args.count > 2
192
+ return if @its_a_dry_run
193
+ puts out = `git clone #{args.first} #{args[1]}`
194
+ repo_name = args[1] || out.split('\'')[1]
195
+ puts 'You have created a copy of ' + args.first.to_s.bold + ' in the ' + repo_name.bold + ' directory.' if $? == 0
196
+ end
197
+
198
+
199
+ def clean!(wipe_ignored, opts)
200
+ x = wipe_ignored ? 'x' : ''
201
+ stdin, stdout, stderr = Open3.popen3("git clean -dfn#{x}")
202
+ out = stderr.readlines
203
+ err = stdout.readlines.join('')
204
+ puts err.red.bold unless opts[:force]
205
+ return puts 'Nothing to clean.'.green if err.empty?
206
+ return if @its_a_dry_run
207
+ run_lambda_with_force_option(opts) do
208
+ puts `git clean -df#{x} #{@dry_run}`
209
+ end
210
+ end
211
+
212
+
213
+ def prompt_for_y_n
214
+ begin
215
+ system("stty raw -echo")
216
+ input = STDIN.getc
217
+ ensure
218
+ system("stty -raw echo")
219
+ end
220
+ out = input.to_s.downcase.eql?('y')
221
+ puts input.to_s
222
+ return out
223
+ end
224
+
225
+
226
+ def run_lambda_with_force_option(opts)
227
+ unless opts[:force]
228
+ print 'proceed(y/n)? '.bold
229
+ return unless prompt_for_y_n
230
+ end
231
+ yield
232
+ end
233
+
234
+
235
+ def reset_hard!(opts)
236
+ return if @its_a_dry_run
237
+ puts 'All changes in tracked files will be lost.'.red.bold unless opts[:force]
238
+ run_lambda_with_force_option(opts) do
239
+ puts `git reset --hard`
240
+ end
241
+ end
242
+
243
+
244
+ def goto!(opts, args)
245
+ return puts "Please specify a commit id.".yellow.bold if args.count < 1
246
+ return puts "Invalid number of arguments. Please specify only a commit id.".yellow.bold if args.count > 1
247
+ commit_id = args[0].to_s
248
+ return puts "Would go to #{commit_id}" if @its_a_dry_run
249
+ puts "About to go to #{commit_id}. All changes in tracked files will be lost.".red.bold unless opts[:force]
250
+ run_lambda_with_force_option(opts) do
251
+ puts `git reset --hard #{commit_id}`
252
+ end
253
+ end
254
+
255
+
256
+ def create(opts, args)
257
+ return puts "Please specify a branch name.".yellow.bold if args.count < 1
258
+ return puts "Invalid number of arguments. Please specify only a branch name.".yellow.bold if args.count > 1
259
+ branch_name = args[0].to_s
260
+ return puts "Would create branch: #{branch_name}" if @its_a_dry_run
261
+ `git checkout -b #{branch_name}`
262
+ display_branch_list_with_current
263
+ display_current_changes
264
+ end
265
+
266
+
267
+ def delete!(opts, args)
268
+ return puts "Please specify a branch name.".yellow.bold if args.count < 1
269
+ return puts "Invalid number of arguments. Please specify only a branch name.".yellow.bold if args.count > 1
270
+ branch_name = args[0].to_s
271
+ is_master = branch_name.eql?('master') || branch_name.eql?(add_remote_to_branch('master'))
272
+ return puts "Cannot delete ".red + branch_name.red.bold + ".".red if is_master
273
+ branches = []
274
+ is_local = all_branches.include?(branch_name)
275
+ branches << branch_name if is_local
276
+ remote_name = add_remote_to_branch(branch_name)
277
+ is_remote = all_branches.include?(remote_name)
278
+ branches << remote_name if is_remote
279
+ return puts "Cannot delete ".red + branch_name.red.bold + " while you are using it. Please switch to another branch and try again.".red if branches.include?(current_branch)
280
+ return puts "Branch does not exist: ".red + branch_name.red.bold unless branches.any?
281
+ return puts "Would completely delete branches: #{branches.join(',')}" if @its_a_dry_run
282
+ print " Are you sure you want to delete '#{branch_name}'(y/n)?".red.bold
283
+ return unless run_lambda_with_force_option(opts) do
284
+ puts `git push --delete #{remote_name.sub('/', ' ')}` if is_remote
285
+ puts `git branch -D #{branch_name}` if is_local
286
+ refresh_branches
287
+ display_branch_list_with_current
288
+ end
289
+ end
290
+
291
+
292
+ #Three modes:
293
+ # :switch - switch if there are not changes. Otherwise halt!
294
+ # :switch! - clobber all files before switching
295
+ # :move - move files with switch
296
+ def switch!(opts, args)
297
+ return puts "Please specify a branch name.".yellow.bold if args.count < 1
298
+ return puts "Invalid number of arguments. Please specify only a branch name.".yellow.bold if args.count > 1
299
+ branch_name = args[0].to_s
300
+ return puts "Please specify a valid branch." unless all_uniq_branches.include?(branch_name)
301
+ return puts "Already on branch: #{current_branch.bold}".green if current_branch.eql?(branch_name)
302
+ has_changes, changes = check_local_changes
303
+ #move files with switch
304
+ if opts[:move]
305
+ x = `git checkout #{branch_name}`
306
+ return
307
+ end
308
+ #switch if there are not changes. Otherwise halt!
309
+ if has_changes && opts[:switch]
310
+ display_current_changes
311
+ puts " Cannot switch branches when you have unresolved changes".red
312
+ puts " Use ".red + "'ez switch! <branch>'".red.bold + " to abandon your changes and switch anyway,".red
313
+ puts " or use ".red + "'ez move <branch>'".red.bold + " to move your changes and switch.".red
314
+ return
315
+ end
316
+ #clobber all files before switching
317
+ #respect the -f option
318
+ if has_changes && opts[:switch!]
319
+ unless opts[:force]
320
+ display_current_changes
321
+ puts ''
322
+ print " WARNING: You may lose changes if you switch branches without committing.".red.bold
323
+ end
324
+ return unless run_lambda_with_force_option(opts) do
325
+ opts[:force] = true
326
+ x = `git clean -df`
327
+ x = `git checkout -f #{branch_name}`
328
+ return
329
+ end
330
+ x = `git clean -df`
331
+ end
332
+ x = `git checkout -f #{branch_name}`
333
+ end
334
+
335
+
336
+ def commit(args)
337
+ return puts "Please specify a message.".yellow.bold if args.count < 1
338
+ return puts "Invalid number of arguments. Please specify only a message.".yellow.bold if args.count > 1
339
+ has_changes, changes = check_local_changes
340
+ return puts "There are no changes to commit".yellow.bold unless has_changes
341
+ commit_id = args[0].to_s
342
+ puts `git add -A`
343
+ puts `git commit -m "#{commit_id}"`
344
+ end
345
+
346
+
347
+ def fetch
348
+ stdin, stdout, stderr = Open3.popen3("git fetch -p #{@dry_run_flag}")
349
+ puts stderr.readlines.join('') + stdout.readlines.join('')
350
+ refresh_branches
351
+ end
352
+
353
+
354
+ def pull
355
+ fetch
356
+ stat, count = check_remote_status
357
+ case stat
358
+ when :rebase
359
+ if @its_a_dry_run
360
+ puts 'would merge changes'
361
+ display_sync_status
362
+ return
363
+ end
364
+ puts `git rebase #{remote_branch}`
365
+ #TODO: CONFLICT HANDLING?
366
+ puts 'TODO: CONFLICT HANDLING?'
367
+ when :behind
368
+ if @its_a_dry_run
369
+ puts "would reset branch to #{remote_branch}"
370
+ display_sync_status
371
+ return
372
+ end
373
+ puts `git reset --hard #{remote_branch}`
374
+ when :headless
375
+ puts ' You cannot pull unless you are on a branch.'.red.bold
376
+ display_sync_status
377
+ return
378
+ end
379
+ info
380
+ end
381
+
382
+
383
+ def push
384
+ stat, count = check_remote_status
385
+ if stat.eql?(:rebase) || stat.eql?(:behind)
386
+ puts " The remote has been updated since you began this sync.".yellow.bold
387
+ puts " Try running 'ez pull' again".yellow.bold
388
+ elsif stat.eql?(:no_remote) || stat.eql?(:ahead)
389
+ puts `git push -u #{remote_branch.sub('/', ' ')}`
390
+ elsif stat.eql?(:headless)
391
+ puts ' You cannot push unless you are on a branch.'.red.bold
392
+ else
393
+ #:up_to_date | :headless
394
+ end
395
+ info
396
+ end
397
+
398
+
399
+ end