jit 0.0.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/LICENSE.txt +674 -0
- data/bin/jit +21 -0
- data/lib/color.rb +32 -0
- data/lib/command.rb +62 -0
- data/lib/command/add.rb +65 -0
- data/lib/command/base.rb +92 -0
- data/lib/command/branch.rb +199 -0
- data/lib/command/checkout.rb +104 -0
- data/lib/command/cherry_pick.rb +51 -0
- data/lib/command/commit.rb +86 -0
- data/lib/command/config.rb +126 -0
- data/lib/command/diff.rb +114 -0
- data/lib/command/fetch.rb +116 -0
- data/lib/command/init.rb +41 -0
- data/lib/command/log.rb +188 -0
- data/lib/command/merge.rb +148 -0
- data/lib/command/push.rb +172 -0
- data/lib/command/receive_pack.rb +92 -0
- data/lib/command/remote.rb +55 -0
- data/lib/command/reset.rb +64 -0
- data/lib/command/rev_list.rb +33 -0
- data/lib/command/revert.rb +69 -0
- data/lib/command/rm.rb +105 -0
- data/lib/command/shared/fast_forward.rb +19 -0
- data/lib/command/shared/print_diff.rb +116 -0
- data/lib/command/shared/receive_objects.rb +37 -0
- data/lib/command/shared/remote_agent.rb +44 -0
- data/lib/command/shared/remote_client.rb +82 -0
- data/lib/command/shared/send_objects.rb +24 -0
- data/lib/command/shared/sequencing.rb +146 -0
- data/lib/command/shared/write_commit.rb +167 -0
- data/lib/command/status.rb +210 -0
- data/lib/command/upload_pack.rb +54 -0
- data/lib/config.rb +240 -0
- data/lib/config/stack.rb +42 -0
- data/lib/database.rb +112 -0
- data/lib/database/author.rb +27 -0
- data/lib/database/backends.rb +57 -0
- data/lib/database/blob.rb +24 -0
- data/lib/database/commit.rb +70 -0
- data/lib/database/entry.rb +7 -0
- data/lib/database/loose.rb +70 -0
- data/lib/database/packed.rb +75 -0
- data/lib/database/tree.rb +77 -0
- data/lib/database/tree_diff.rb +88 -0
- data/lib/diff.rb +46 -0
- data/lib/diff/combined.rb +72 -0
- data/lib/diff/hunk.rb +64 -0
- data/lib/diff/myers.rb +90 -0
- data/lib/editor.rb +59 -0
- data/lib/index.rb +212 -0
- data/lib/index/checksum.rb +44 -0
- data/lib/index/entry.rb +91 -0
- data/lib/lockfile.rb +55 -0
- data/lib/merge/bases.rb +38 -0
- data/lib/merge/common_ancestors.rb +77 -0
- data/lib/merge/diff3.rb +156 -0
- data/lib/merge/inputs.rb +42 -0
- data/lib/merge/resolve.rb +178 -0
- data/lib/pack.rb +45 -0
- data/lib/pack/compressor.rb +83 -0
- data/lib/pack/delta.rb +58 -0
- data/lib/pack/entry.rb +54 -0
- data/lib/pack/expander.rb +54 -0
- data/lib/pack/index.rb +100 -0
- data/lib/pack/indexer.rb +200 -0
- data/lib/pack/numbers.rb +79 -0
- data/lib/pack/reader.rb +98 -0
- data/lib/pack/stream.rb +80 -0
- data/lib/pack/unpacker.rb +62 -0
- data/lib/pack/window.rb +47 -0
- data/lib/pack/writer.rb +92 -0
- data/lib/pack/xdelta.rb +118 -0
- data/lib/pager.rb +24 -0
- data/lib/progress.rb +78 -0
- data/lib/refs.rb +260 -0
- data/lib/remotes.rb +82 -0
- data/lib/remotes/protocol.rb +82 -0
- data/lib/remotes/refspec.rb +70 -0
- data/lib/remotes/remote.rb +57 -0
- data/lib/repository.rb +64 -0
- data/lib/repository/divergence.rb +21 -0
- data/lib/repository/hard_reset.rb +35 -0
- data/lib/repository/inspector.rb +49 -0
- data/lib/repository/migration.rb +168 -0
- data/lib/repository/pending_commit.rb +60 -0
- data/lib/repository/sequencer.rb +118 -0
- data/lib/repository/status.rb +98 -0
- data/lib/rev_list.rb +244 -0
- data/lib/revision.rb +155 -0
- data/lib/sorted_hash.rb +17 -0
- data/lib/temp_file.rb +34 -0
- data/lib/workspace.rb +107 -0
- metadata +103 -9
@@ -0,0 +1,104 @@
|
|
1
|
+
require_relative "./base"
|
2
|
+
require_relative "../revision"
|
3
|
+
|
4
|
+
module Command
|
5
|
+
class Checkout < Base
|
6
|
+
|
7
|
+
DETACHED_HEAD_MESSAGE = <<~MSG
|
8
|
+
You are in 'detached HEAD' state. You can look around, make experimental
|
9
|
+
changes and commit them, and you can discard any commits you make in this
|
10
|
+
state without impacting any branches by performing another checkout.
|
11
|
+
|
12
|
+
If you want to create a new branch to retain commits you create, you may
|
13
|
+
do so (now or later) by using the branch command. Example:
|
14
|
+
|
15
|
+
jit branch <new-branch-name>
|
16
|
+
MSG
|
17
|
+
|
18
|
+
def run
|
19
|
+
@target = @args[0]
|
20
|
+
|
21
|
+
@current_ref = repo.refs.current_ref
|
22
|
+
@current_oid = @current_ref.read_oid
|
23
|
+
|
24
|
+
revision = Revision.new(repo, @target)
|
25
|
+
@target_oid = revision.resolve(Revision::COMMIT)
|
26
|
+
|
27
|
+
repo.index.load_for_update
|
28
|
+
|
29
|
+
tree_diff = repo.database.tree_diff(@current_oid, @target_oid)
|
30
|
+
migration = repo.migration(tree_diff)
|
31
|
+
migration.apply_changes
|
32
|
+
|
33
|
+
repo.index.write_updates
|
34
|
+
repo.refs.set_head(@target, @target_oid)
|
35
|
+
@new_ref = repo.refs.current_ref
|
36
|
+
|
37
|
+
print_previous_head
|
38
|
+
print_detachment_notice
|
39
|
+
print_new_head
|
40
|
+
|
41
|
+
exit 0
|
42
|
+
|
43
|
+
rescue Repository::Migration::Conflict
|
44
|
+
handle_migration_conflict(migration)
|
45
|
+
|
46
|
+
rescue Revision::InvalidObject => error
|
47
|
+
handle_invalid_object(revision, error)
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def handle_migration_conflict(migration)
|
53
|
+
repo.index.release_lock
|
54
|
+
|
55
|
+
migration.errors.each do |message|
|
56
|
+
@stderr.puts "error: #{ message }"
|
57
|
+
end
|
58
|
+
@stderr.puts "Aborting"
|
59
|
+
exit 1
|
60
|
+
end
|
61
|
+
|
62
|
+
def handle_invalid_object(revision, error)
|
63
|
+
revision.errors.each do |err|
|
64
|
+
@stderr.puts "error: #{ err.message }"
|
65
|
+
err.hint.each { |line| @stderr.puts "hint: #{ line }" }
|
66
|
+
end
|
67
|
+
@stderr.puts "error: #{ error.message }"
|
68
|
+
exit 1
|
69
|
+
end
|
70
|
+
|
71
|
+
def print_previous_head
|
72
|
+
if @current_ref.head? and @current_oid != @target_oid
|
73
|
+
print_head_position("Previous HEAD position was", @current_oid)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def print_detachment_notice
|
78
|
+
return unless @new_ref.head? and not @current_ref.head?
|
79
|
+
|
80
|
+
@stderr.puts "Note: checking out '#{ @target }'."
|
81
|
+
@stderr.puts ""
|
82
|
+
@stderr.puts DETACHED_HEAD_MESSAGE
|
83
|
+
@stderr.puts ""
|
84
|
+
end
|
85
|
+
|
86
|
+
def print_new_head
|
87
|
+
if @new_ref.head?
|
88
|
+
print_head_position("HEAD is now at", @target_oid)
|
89
|
+
elsif @new_ref == @current_ref
|
90
|
+
@stderr.puts "Already on '#{ @target }'"
|
91
|
+
else
|
92
|
+
@stderr.puts "Switched to branch '#{ @target }'"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def print_head_position(message, oid)
|
97
|
+
commit = repo.database.load(oid)
|
98
|
+
short = repo.database.short_oid(commit.oid)
|
99
|
+
|
100
|
+
@stderr.puts "#{ message } #{ short } #{ commit.title_line }"
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require_relative "./base"
|
2
|
+
require_relative "./shared/sequencing"
|
3
|
+
require_relative "./shared/write_commit"
|
4
|
+
require_relative "../merge/inputs"
|
5
|
+
require_relative "../rev_list"
|
6
|
+
|
7
|
+
module Command
|
8
|
+
class CherryPick < Base
|
9
|
+
|
10
|
+
include Sequencing
|
11
|
+
include WriteCommit
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def merge_type
|
16
|
+
:cherry_pick
|
17
|
+
end
|
18
|
+
|
19
|
+
def store_commit_sequence
|
20
|
+
commits = ::RevList.new(repo, @args.reverse, :walk => false)
|
21
|
+
commits.reverse_each { |commit| sequencer.pick(commit) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def pick(commit)
|
25
|
+
inputs = pick_merge_inputs(commit)
|
26
|
+
resolve_merge(inputs)
|
27
|
+
fail_on_conflict(inputs, commit.message) if repo.index.conflict?
|
28
|
+
|
29
|
+
picked = Database::Commit.new([inputs.left_oid], write_tree.oid,
|
30
|
+
commit.author, current_author,
|
31
|
+
commit.message)
|
32
|
+
|
33
|
+
finish_commit(picked)
|
34
|
+
end
|
35
|
+
|
36
|
+
def pick_merge_inputs(commit)
|
37
|
+
short = repo.database.short_oid(commit.oid)
|
38
|
+
parent = select_parent(commit)
|
39
|
+
|
40
|
+
left_name = Refs::HEAD
|
41
|
+
left_oid = repo.refs.read_head
|
42
|
+
right_name = "#{ short }... #{ commit.title_line.strip }"
|
43
|
+
right_oid = commit.oid
|
44
|
+
|
45
|
+
::Merge::CherryPick.new(left_name, right_name,
|
46
|
+
left_oid, right_oid,
|
47
|
+
[parent])
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require "pathname"
|
2
|
+
require_relative "./base"
|
3
|
+
require_relative "./shared/write_commit"
|
4
|
+
require_relative "../revision"
|
5
|
+
|
6
|
+
module Command
|
7
|
+
class Commit < Base
|
8
|
+
|
9
|
+
include WriteCommit
|
10
|
+
|
11
|
+
COMMIT_NOTES = <<~MSG
|
12
|
+
Please enter the commit message for your changes. Lines starting
|
13
|
+
with '#' will be ignored, and an empty message aborts the commit.
|
14
|
+
MSG
|
15
|
+
|
16
|
+
def define_options
|
17
|
+
define_write_commit_options
|
18
|
+
|
19
|
+
@parser.on("--amend") { @options[:amend] = true }
|
20
|
+
|
21
|
+
@parser.on "-C <commit>", "--reuse-message=<commit>" do |commit|
|
22
|
+
@options[:reuse] = commit
|
23
|
+
@options[:edit] = false
|
24
|
+
end
|
25
|
+
|
26
|
+
@parser.on "-c <commit>", "--reedit-message=<commit>" do |commit|
|
27
|
+
@options[:reuse] = commit
|
28
|
+
@options[:edit] = true
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def run
|
33
|
+
repo.index.load
|
34
|
+
|
35
|
+
handle_amend if @options[:amend]
|
36
|
+
|
37
|
+
merge_type = pending_commit.merge_type
|
38
|
+
resume_merge(merge_type) if merge_type
|
39
|
+
|
40
|
+
parent = repo.refs.read_head
|
41
|
+
message = compose_message(read_message || reused_message)
|
42
|
+
commit = write_commit([*parent], message)
|
43
|
+
|
44
|
+
print_commit(commit)
|
45
|
+
|
46
|
+
exit 0
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def compose_message(message)
|
52
|
+
edit_file(commit_message_path) do |editor|
|
53
|
+
editor.puts(message || "")
|
54
|
+
editor.puts("")
|
55
|
+
editor.note(COMMIT_NOTES)
|
56
|
+
|
57
|
+
editor.close unless @options[:edit]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def reused_message
|
62
|
+
return nil unless @options.has_key?(:reuse)
|
63
|
+
|
64
|
+
revision = Revision.new(repo, @options[:reuse])
|
65
|
+
commit = repo.database.load(revision.resolve)
|
66
|
+
|
67
|
+
commit.message
|
68
|
+
end
|
69
|
+
|
70
|
+
def handle_amend
|
71
|
+
old = repo.database.load(repo.refs.read_head)
|
72
|
+
tree = write_tree
|
73
|
+
|
74
|
+
message = compose_message(old.message)
|
75
|
+
committer = current_author
|
76
|
+
|
77
|
+
new = Database::Commit.new(old.parents, tree.oid, old.author, committer, message)
|
78
|
+
repo.database.store(new)
|
79
|
+
repo.refs.update_head(new.oid)
|
80
|
+
|
81
|
+
print_commit(new)
|
82
|
+
exit 0
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
require_relative "./base"
|
2
|
+
|
3
|
+
module Command
|
4
|
+
class Config < Base
|
5
|
+
|
6
|
+
def define_options
|
7
|
+
@parser.on("--local") { @options[:file] = :local }
|
8
|
+
@parser.on("--global") { @options[:file] = :global }
|
9
|
+
@parser.on("--system") { @options[:file] = :system }
|
10
|
+
|
11
|
+
@parser.on "-f <config-file>", "--file=<config-file>" do |file|
|
12
|
+
@options[:file] = file
|
13
|
+
end
|
14
|
+
|
15
|
+
@parser.on("--add <name>") { |name| @options[:add] = name }
|
16
|
+
@parser.on("--replace-all <name>") { |name| @options[:replace] = name }
|
17
|
+
@parser.on("--get-all <name>") { |name| @options[:get_all] = name }
|
18
|
+
@parser.on("--unset <name>") { |name| @options[:unset] = name }
|
19
|
+
@parser.on("--unset-all <name>") { |name| @options[:unset_all] = name }
|
20
|
+
|
21
|
+
@parser.on "--remove-section <name>" do |name|
|
22
|
+
@options[:remove_section] = name
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def run
|
27
|
+
add_variable if @options[:add]
|
28
|
+
replace_variable if @options[:replace]
|
29
|
+
get_all_values if @options[:get_all]
|
30
|
+
unset_single if @options[:unset]
|
31
|
+
unset_all if @options[:unset_all]
|
32
|
+
remove_section if @options[:remove_section]
|
33
|
+
|
34
|
+
key, value = parse_key(@args[0]), @args[1]
|
35
|
+
|
36
|
+
if value
|
37
|
+
edit_config { |config| config.set(key, value) }
|
38
|
+
else
|
39
|
+
read_config { |config| [*config.get(key)] }
|
40
|
+
end
|
41
|
+
|
42
|
+
rescue ::Config::ParseError => error
|
43
|
+
@stderr.puts "error: #{ error.message }"
|
44
|
+
exit 3
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def add_variable
|
50
|
+
key = parse_key(@options[:add])
|
51
|
+
edit_config { |config| config.add(key, @args[0]) }
|
52
|
+
end
|
53
|
+
|
54
|
+
def replace_variable
|
55
|
+
key = parse_key(@options[:replace])
|
56
|
+
edit_config { |config| config.replace_all(key, @args[0]) }
|
57
|
+
end
|
58
|
+
|
59
|
+
def unset_single
|
60
|
+
key = parse_key(@options[:unset])
|
61
|
+
edit_config { |config| config.unset(key) }
|
62
|
+
end
|
63
|
+
|
64
|
+
def unset_all
|
65
|
+
key = parse_key(@options[:unset_all])
|
66
|
+
edit_config { |config| config.unset_all(key) }
|
67
|
+
end
|
68
|
+
|
69
|
+
def remove_section
|
70
|
+
key = @options[:remove_section].split(".", 2)
|
71
|
+
edit_config { |config| config.remove_section(key) }
|
72
|
+
end
|
73
|
+
|
74
|
+
def get_all_values
|
75
|
+
key = parse_key(@options[:get_all])
|
76
|
+
read_config { |config| config.get_all(key) }
|
77
|
+
end
|
78
|
+
|
79
|
+
def read_config
|
80
|
+
config = repo.config
|
81
|
+
config = config.file(@options[:file]) if @options[:file]
|
82
|
+
|
83
|
+
config.open
|
84
|
+
values = yield config
|
85
|
+
|
86
|
+
exit 1 if values.empty?
|
87
|
+
|
88
|
+
values.each { |value| puts value }
|
89
|
+
exit 0
|
90
|
+
end
|
91
|
+
|
92
|
+
def edit_config
|
93
|
+
config = repo.config.file(@options.fetch(:file, :local))
|
94
|
+
config.open_for_update
|
95
|
+
yield config
|
96
|
+
config.save
|
97
|
+
|
98
|
+
exit 0
|
99
|
+
|
100
|
+
rescue ::Config::Conflict => error
|
101
|
+
@stderr.puts "error: #{ error.message }"
|
102
|
+
exit 5
|
103
|
+
end
|
104
|
+
|
105
|
+
def parse_key(name)
|
106
|
+
section, *subsection, var = name.split(".")
|
107
|
+
|
108
|
+
unless var
|
109
|
+
@stderr.puts "error: key does not contain a section: #{ name }"
|
110
|
+
exit 2
|
111
|
+
end
|
112
|
+
|
113
|
+
unless ::Config.valid_key?([section, var])
|
114
|
+
@stderr.puts "error: invalid key: #{ name }"
|
115
|
+
exit 1
|
116
|
+
end
|
117
|
+
|
118
|
+
if subsection.empty?
|
119
|
+
[section, var]
|
120
|
+
else
|
121
|
+
[section, subsection.join("."), var]
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
end
|
data/lib/command/diff.rb
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
require_relative "./base"
|
2
|
+
require_relative "./shared/print_diff"
|
3
|
+
|
4
|
+
module Command
|
5
|
+
class Diff < Base
|
6
|
+
|
7
|
+
include PrintDiff
|
8
|
+
|
9
|
+
def define_options
|
10
|
+
@options[:patch] = true
|
11
|
+
define_print_diff_options
|
12
|
+
|
13
|
+
@parser.on "--cached", "--staged" do
|
14
|
+
@options[:cached] = true
|
15
|
+
end
|
16
|
+
|
17
|
+
@parser.on("-1", "--base") { @options[:stage] = 1 }
|
18
|
+
@parser.on("-2", "--ours") { @options[:stage] = 2 }
|
19
|
+
@parser.on("-3", "--theirs") { @options[:stage] = 3 }
|
20
|
+
end
|
21
|
+
|
22
|
+
def run
|
23
|
+
repo.index.load
|
24
|
+
@status = repo.status
|
25
|
+
|
26
|
+
setup_pager
|
27
|
+
|
28
|
+
if @options[:cached]
|
29
|
+
diff_head_index
|
30
|
+
else
|
31
|
+
diff_index_workspace
|
32
|
+
end
|
33
|
+
|
34
|
+
exit 0
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def diff_head_index
|
40
|
+
return unless @options[:patch]
|
41
|
+
|
42
|
+
@status.index_changes.each do |path, state|
|
43
|
+
case state
|
44
|
+
when :added then print_diff(from_nothing(path), from_index(path))
|
45
|
+
when :modified then print_diff(from_head(path), from_index(path))
|
46
|
+
when :deleted then print_diff(from_head(path), from_nothing(path))
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def diff_index_workspace
|
52
|
+
return unless @options[:patch]
|
53
|
+
|
54
|
+
paths = @status.conflicts.keys + @status.workspace_changes.keys
|
55
|
+
|
56
|
+
paths.sort.each do |path|
|
57
|
+
if @status.conflicts.has_key?(path)
|
58
|
+
print_conflict_diff(path)
|
59
|
+
else
|
60
|
+
print_workspace_diff(path)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def print_conflict_diff(path)
|
66
|
+
targets = (0..3).map { |stage| from_index(path, stage) }
|
67
|
+
left, right = targets[2], targets[3]
|
68
|
+
|
69
|
+
if @options[:stage]
|
70
|
+
puts "* Unmerged path #{ path }"
|
71
|
+
print_diff(targets[@options[:stage]], from_file(path))
|
72
|
+
elsif left and right
|
73
|
+
print_combined_diff([left, right], from_file(path))
|
74
|
+
else
|
75
|
+
puts "* Unmerged path #{ path }"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def print_workspace_diff(path)
|
80
|
+
case @status.workspace_changes[path]
|
81
|
+
when :modified then print_diff(from_index(path), from_file(path))
|
82
|
+
when :deleted then print_diff(from_index(path), from_nothing(path))
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def from_head(path)
|
87
|
+
entry = @status.head_tree.fetch(path)
|
88
|
+
blob = repo.database.load(entry.oid)
|
89
|
+
|
90
|
+
Target.new(path, entry.oid, entry.mode.to_s(8), blob.data)
|
91
|
+
end
|
92
|
+
|
93
|
+
def from_index(path, stage = 0)
|
94
|
+
entry = repo.index.entry_for_path(path, stage)
|
95
|
+
return nil unless entry
|
96
|
+
|
97
|
+
blob = repo.database.load(entry.oid)
|
98
|
+
Target.new(path, entry.oid, entry.mode.to_s(8), blob.data)
|
99
|
+
end
|
100
|
+
|
101
|
+
def from_file(path)
|
102
|
+
blob = Database::Blob.new(repo.workspace.read_file(path))
|
103
|
+
oid = repo.database.hash_object(blob)
|
104
|
+
mode = Index::Entry.mode_for_stat(@status.stats[path])
|
105
|
+
|
106
|
+
Target.new(path, oid, mode.to_s(8), blob.data)
|
107
|
+
end
|
108
|
+
|
109
|
+
def from_nothing(path)
|
110
|
+
Target.new(path, NULL_OID, nil, "")
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
end
|