jit 0.0.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. checksums.yaml +5 -5
  2. data/LICENSE.txt +674 -0
  3. data/bin/jit +21 -0
  4. data/lib/color.rb +32 -0
  5. data/lib/command.rb +62 -0
  6. data/lib/command/add.rb +65 -0
  7. data/lib/command/base.rb +92 -0
  8. data/lib/command/branch.rb +199 -0
  9. data/lib/command/checkout.rb +104 -0
  10. data/lib/command/cherry_pick.rb +51 -0
  11. data/lib/command/commit.rb +86 -0
  12. data/lib/command/config.rb +126 -0
  13. data/lib/command/diff.rb +114 -0
  14. data/lib/command/fetch.rb +116 -0
  15. data/lib/command/init.rb +41 -0
  16. data/lib/command/log.rb +188 -0
  17. data/lib/command/merge.rb +148 -0
  18. data/lib/command/push.rb +172 -0
  19. data/lib/command/receive_pack.rb +92 -0
  20. data/lib/command/remote.rb +55 -0
  21. data/lib/command/reset.rb +64 -0
  22. data/lib/command/rev_list.rb +33 -0
  23. data/lib/command/revert.rb +69 -0
  24. data/lib/command/rm.rb +105 -0
  25. data/lib/command/shared/fast_forward.rb +19 -0
  26. data/lib/command/shared/print_diff.rb +116 -0
  27. data/lib/command/shared/receive_objects.rb +37 -0
  28. data/lib/command/shared/remote_agent.rb +44 -0
  29. data/lib/command/shared/remote_client.rb +82 -0
  30. data/lib/command/shared/send_objects.rb +24 -0
  31. data/lib/command/shared/sequencing.rb +146 -0
  32. data/lib/command/shared/write_commit.rb +167 -0
  33. data/lib/command/status.rb +210 -0
  34. data/lib/command/upload_pack.rb +54 -0
  35. data/lib/config.rb +240 -0
  36. data/lib/config/stack.rb +42 -0
  37. data/lib/database.rb +112 -0
  38. data/lib/database/author.rb +27 -0
  39. data/lib/database/backends.rb +57 -0
  40. data/lib/database/blob.rb +24 -0
  41. data/lib/database/commit.rb +70 -0
  42. data/lib/database/entry.rb +7 -0
  43. data/lib/database/loose.rb +70 -0
  44. data/lib/database/packed.rb +75 -0
  45. data/lib/database/tree.rb +77 -0
  46. data/lib/database/tree_diff.rb +88 -0
  47. data/lib/diff.rb +46 -0
  48. data/lib/diff/combined.rb +72 -0
  49. data/lib/diff/hunk.rb +64 -0
  50. data/lib/diff/myers.rb +90 -0
  51. data/lib/editor.rb +59 -0
  52. data/lib/index.rb +212 -0
  53. data/lib/index/checksum.rb +44 -0
  54. data/lib/index/entry.rb +91 -0
  55. data/lib/lockfile.rb +55 -0
  56. data/lib/merge/bases.rb +38 -0
  57. data/lib/merge/common_ancestors.rb +77 -0
  58. data/lib/merge/diff3.rb +156 -0
  59. data/lib/merge/inputs.rb +42 -0
  60. data/lib/merge/resolve.rb +178 -0
  61. data/lib/pack.rb +45 -0
  62. data/lib/pack/compressor.rb +83 -0
  63. data/lib/pack/delta.rb +58 -0
  64. data/lib/pack/entry.rb +54 -0
  65. data/lib/pack/expander.rb +54 -0
  66. data/lib/pack/index.rb +100 -0
  67. data/lib/pack/indexer.rb +200 -0
  68. data/lib/pack/numbers.rb +79 -0
  69. data/lib/pack/reader.rb +98 -0
  70. data/lib/pack/stream.rb +80 -0
  71. data/lib/pack/unpacker.rb +62 -0
  72. data/lib/pack/window.rb +47 -0
  73. data/lib/pack/writer.rb +92 -0
  74. data/lib/pack/xdelta.rb +118 -0
  75. data/lib/pager.rb +24 -0
  76. data/lib/progress.rb +78 -0
  77. data/lib/refs.rb +260 -0
  78. data/lib/remotes.rb +82 -0
  79. data/lib/remotes/protocol.rb +82 -0
  80. data/lib/remotes/refspec.rb +70 -0
  81. data/lib/remotes/remote.rb +57 -0
  82. data/lib/repository.rb +64 -0
  83. data/lib/repository/divergence.rb +21 -0
  84. data/lib/repository/hard_reset.rb +35 -0
  85. data/lib/repository/inspector.rb +49 -0
  86. data/lib/repository/migration.rb +168 -0
  87. data/lib/repository/pending_commit.rb +60 -0
  88. data/lib/repository/sequencer.rb +118 -0
  89. data/lib/repository/status.rb +98 -0
  90. data/lib/rev_list.rb +244 -0
  91. data/lib/revision.rb +155 -0
  92. data/lib/sorted_hash.rb +17 -0
  93. data/lib/temp_file.rb +34 -0
  94. data/lib/workspace.rb +107 -0
  95. metadata +103 -9
@@ -0,0 +1,116 @@
1
+ require "set"
2
+
3
+ require_relative "./base"
4
+ require_relative "./shared/fast_forward"
5
+ require_relative "./shared/receive_objects"
6
+ require_relative "./shared/remote_client"
7
+ require_relative "../remotes"
8
+ require_relative "../rev_list"
9
+
10
+ module Command
11
+ class Fetch < Base
12
+
13
+ include FastForward
14
+ include ReceiveObjects
15
+ include RemoteClient
16
+
17
+ CAPABILITIES = ["ofs-delta"]
18
+ UPLOAD_PACK = "git-upload-pack"
19
+
20
+ def define_options
21
+ @parser.on("-f", "--force") { @options[:force] = true }
22
+
23
+ @parser.on "--upload-pack=<upload-pack>" do |uploader|
24
+ @options[:uploader] = uploader
25
+ end
26
+ end
27
+
28
+ def run
29
+ configure
30
+ start_agent("fetch", @uploader, @fetch_url, CAPABILITIES)
31
+
32
+ recv_references
33
+ send_want_list
34
+ send_have_list
35
+ recv_objects
36
+ update_remote_refs
37
+
38
+ exit (@errors.empty? ? 0 : 1)
39
+ end
40
+
41
+ private
42
+
43
+ def configure
44
+ current_branch = repo.refs.current_ref.short_name
45
+ branch_remote = repo.config.get(["branch", current_branch, "remote"])
46
+
47
+ name = @args.fetch(0, branch_remote || Remotes::DEFAULT_REMOTE)
48
+ remote = repo.remotes.get(name)
49
+
50
+ @fetch_url = remote&.fetch_url || @args[0]
51
+ @uploader = @options[:uploader] || remote&.uploader || UPLOAD_PACK
52
+ @fetch_specs = (@args.size > 1) ? @args.drop(1) : remote&.fetch_specs
53
+ end
54
+
55
+ def send_want_list
56
+ @targets = Remotes::Refspec.expand(@fetch_specs, @remote_refs.keys)
57
+ wanted = Set.new
58
+
59
+ @local_refs = {}
60
+
61
+ @targets.each do |target, (source, _)|
62
+ local_oid = repo.refs.read_ref(target)
63
+ remote_oid = @remote_refs[source]
64
+
65
+ next if local_oid == remote_oid
66
+
67
+ @local_refs[target] = local_oid
68
+ wanted.add(remote_oid)
69
+ end
70
+
71
+ wanted.each { |oid| @conn.send_packet("want #{ oid }") }
72
+ @conn.send_packet(nil)
73
+
74
+ exit 0 if wanted.empty?
75
+ end
76
+
77
+ def send_have_list
78
+ options = { :all => true, :missing => true }
79
+ rev_list = ::RevList.new(repo, [], options)
80
+
81
+ rev_list.each { |commit| @conn.send_packet("have #{ commit.oid }") }
82
+ @conn.send_packet("done")
83
+
84
+ @conn.recv_until(Pack::SIGNATURE) {}
85
+ end
86
+
87
+ def recv_objects
88
+ unpack_limit = repo.config.get(["fetch", "unpackLimit"])
89
+ recv_packed_objects(unpack_limit, Pack::SIGNATURE)
90
+ end
91
+
92
+ def update_remote_refs
93
+ @stderr.puts "From #{ @fetch_url }"
94
+
95
+ @errors = {}
96
+ @local_refs.each { |target, oid| attempt_ref_update(target, oid) }
97
+ end
98
+
99
+ def attempt_ref_update(target, old_oid)
100
+ source, forced = @targets[target]
101
+
102
+ new_oid = @remote_refs[source]
103
+ ref_names = [source, target]
104
+ ff_error = fast_forward_error(old_oid, new_oid)
105
+
106
+ if @options[:force] or forced or ff_error == nil
107
+ repo.refs.update_ref(target, new_oid)
108
+ else
109
+ error = @errors[target] = ff_error
110
+ end
111
+
112
+ report_ref_update(ref_names, error, old_oid, new_oid, ff_error == nil)
113
+ end
114
+
115
+ end
116
+ end
@@ -0,0 +1,41 @@
1
+ require "fileutils"
2
+
3
+ require_relative "./base"
4
+ require_relative "../config"
5
+ require_relative "../refs"
6
+
7
+ module Command
8
+ class Init < Base
9
+
10
+ DEFAULT_BRANCH = "master"
11
+
12
+ def run
13
+ path = @args.fetch(0, @dir)
14
+
15
+ root_path = expanded_pathname(path)
16
+ git_path = root_path.join(".git")
17
+
18
+ ["objects", "refs/heads"].each do |dir|
19
+ begin
20
+ FileUtils.mkdir_p(git_path.join(dir))
21
+ rescue Errno::EACCES => error
22
+ @stderr.puts "fatal: #{ error.message }"
23
+ exit 1
24
+ end
25
+ end
26
+
27
+ config = ::Config.new(git_path.join("config"))
28
+ config.open_for_update
29
+ config.set(["core", "bare"], false)
30
+ config.save
31
+
32
+ refs = Refs.new(git_path)
33
+ path = File.join("refs", "heads", DEFAULT_BRANCH)
34
+ refs.update_head("ref: #{ path }")
35
+
36
+ puts "Initialized empty Jit repository in #{ git_path }"
37
+ exit 0
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,188 @@
1
+ require_relative "./base"
2
+ require_relative "./shared/print_diff"
3
+ require_relative "../rev_list"
4
+
5
+ module Command
6
+ class Log < Base
7
+
8
+ include PrintDiff
9
+
10
+ def define_options
11
+ @rev_list_opts = {}
12
+ @parser.on("--all") { @rev_list_opts[:all] = true }
13
+ @parser.on("--branches") { @rev_list_opts[:branches] = true }
14
+ @parser.on("--remotes") { @rev_list_opts[:remotes] = true }
15
+
16
+ @options[:decorate] = "auto"
17
+ @options[:abbrev] = :auto
18
+ @options[:format] = "medium"
19
+ @options[:patch] = false
20
+
21
+ define_print_diff_options
22
+
23
+ @parser.on "--decorate[=<format>]" do |format|
24
+ @options[:decorate] = format || "short"
25
+ end
26
+
27
+ @parser.on "--no-decorate" do
28
+ @options[:decorate] = "no"
29
+ end
30
+
31
+ @parser.on "--[no-]abbrev-commit" do |value|
32
+ @options[:abbrev] = value
33
+ end
34
+
35
+ @parser.on "--pretty=<format>", "--format=<format>" do |format|
36
+ @options[:format] = format
37
+ end
38
+
39
+ @parser.on "--oneline" do
40
+ @options[:abbrev] = true if @options[:abbrev] == :auto
41
+ @options[:format] = "oneline"
42
+ end
43
+
44
+ @parser.on "--cc" do
45
+ @options[:combined] = @options[:patch] = true
46
+ end
47
+ end
48
+
49
+ def run
50
+ setup_pager
51
+
52
+ @reverse_refs = repo.refs.reverse_refs
53
+ @current_ref = repo.refs.current_ref
54
+
55
+ @rev_list = ::RevList.new(repo, @args, @rev_list_opts)
56
+ @rev_list.each { |commit| show_commit(commit) }
57
+
58
+ exit 0
59
+ end
60
+
61
+ private
62
+
63
+ def blank_line
64
+ return if @options[:format] == "oneline"
65
+ puts "" if defined? @blank_line
66
+ @blank_line = true
67
+ end
68
+
69
+ def show_commit(commit)
70
+ case @options[:format]
71
+ when "medium" then show_commit_medium(commit)
72
+ when "oneline" then show_commit_oneline(commit)
73
+ end
74
+
75
+ show_patch(commit)
76
+ end
77
+
78
+ def show_commit_medium(commit)
79
+ author = commit.author
80
+
81
+ blank_line
82
+ puts fmt(:yellow, "commit #{ abbrev(commit) }") + decorate(commit)
83
+
84
+ if commit.merge?
85
+ oids = commit.parents.map { |oid| repo.database.short_oid(oid) }
86
+ puts "Merge: #{ oids.join(" ") }"
87
+ end
88
+
89
+ puts "Author: #{ author.name } <#{ author.email }>"
90
+ puts "Date: #{ author.readable_time }"
91
+ blank_line
92
+ commit.message.each_line { |line| puts " #{ line }" }
93
+ end
94
+
95
+ def show_commit_oneline(commit)
96
+ id = fmt(:yellow, abbrev(commit)) + decorate(commit)
97
+ puts "#{ id } #{ commit.title_line }"
98
+ end
99
+
100
+ def abbrev(commit)
101
+ if @options[:abbrev] == true
102
+ repo.database.short_oid(commit.oid)
103
+ else
104
+ commit.oid
105
+ end
106
+ end
107
+
108
+ def decorate(commit)
109
+ case @options[:decorate]
110
+ when "auto" then return "" unless @isatty
111
+ when "no" then return ""
112
+ end
113
+
114
+ refs = @reverse_refs[commit.oid]
115
+ return "" if refs.empty?
116
+
117
+ head, refs = refs.partition { |ref| ref.head? and not @current_ref.head? }
118
+ names = refs.map { |ref| decoration_name(head.first, ref) }
119
+
120
+ fmt(:yellow, " (") + names.join(fmt(:yellow, ", ")) + fmt(:yellow, ")")
121
+ end
122
+
123
+ def decoration_name(head, ref)
124
+ case @options[:decorate]
125
+ when "short", "auto" then name = ref.short_name
126
+ when "full" then name = ref.path
127
+ end
128
+
129
+ name = fmt(ref_color(ref), name)
130
+
131
+ if head and ref == @current_ref
132
+ name = fmt(ref_color(head), "#{ head.path } -> #{ name }")
133
+ end
134
+
135
+ name
136
+ end
137
+
138
+ def ref_color(ref)
139
+ return [:bold, :cyan] if ref.head?
140
+ return [:bold, :green] if ref.branch?
141
+ return [:bold, :red] if ref.remote?
142
+ end
143
+
144
+ def show_patch(commit)
145
+ return unless @options[:patch]
146
+ return show_merge_patch(commit) if commit.merge?
147
+
148
+ diff = @rev_list.tree_diff(commit.parent, commit.oid)
149
+ paths = diff.keys.sort_by(&:to_s)
150
+
151
+ blank_line
152
+
153
+ paths.each do |path|
154
+ old_item, new_item = diff[path]
155
+ print_diff(from_diff_item(path, old_item), from_diff_item(path, new_item))
156
+ end
157
+ end
158
+
159
+ def show_merge_patch(commit)
160
+ return unless @options[:combined]
161
+
162
+ diffs = commit.parents.map { |oid| @rev_list.tree_diff(oid, commit.oid) }
163
+
164
+ paths = diffs.first.keys.select do |path|
165
+ diffs.drop(1).all? { |diff| diff.has_key?(path) }
166
+ end
167
+
168
+ blank_line
169
+
170
+ paths.each do |path|
171
+ parents = diffs.map { |diff| from_diff_item(path, diff[path][0]) }
172
+ child = from_diff_item(path, diffs.first[path][1])
173
+
174
+ print_combined_diff(parents, child)
175
+ end
176
+ end
177
+
178
+ def from_diff_item(path, item)
179
+ if item
180
+ blob = repo.database.load(item.oid)
181
+ Target.new(path, item.oid, item.mode.to_s(8), blob.data)
182
+ else
183
+ Target.new(path, NULL_OID, nil, "")
184
+ end
185
+ end
186
+
187
+ end
188
+ end
@@ -0,0 +1,148 @@
1
+ require_relative "./base"
2
+ require_relative "./shared/write_commit"
3
+ require_relative "../merge/inputs"
4
+ require_relative "../merge/resolve"
5
+ require_relative "../revision"
6
+
7
+ module Command
8
+ class Merge < Base
9
+
10
+ include WriteCommit
11
+
12
+ COMMIT_NOTES = <<~MSG
13
+ Please enter a commit message to explain why this merge is necessary,
14
+ especially if it merges an updated upstream into a topic branch.
15
+
16
+ Lines starting with '#' will be ignored, and an empty message aborts
17
+ the commit.
18
+ MSG
19
+
20
+ def define_options
21
+ define_write_commit_options
22
+
23
+ @options[:mode] = :run
24
+
25
+ @parser.on("--abort") { @options[:mode] = :abort }
26
+ @parser.on("--continue") { @options[:mode] = :continue }
27
+ end
28
+
29
+ def run
30
+ handle_abort if @options[:mode] == :abort
31
+ handle_continue if @options[:mode] == :continue
32
+ handle_in_progress_merge if pending_commit.in_progress?
33
+
34
+ @inputs = ::Merge::Inputs.new(repo, Revision::HEAD, @args[0])
35
+ repo.refs.update_ref(Refs::ORIG_HEAD, @inputs.left_oid)
36
+
37
+ handle_merged_ancestor if @inputs.already_merged?
38
+ handle_fast_forward if @inputs.fast_forward?
39
+
40
+ pending_commit.start(@inputs.right_oid)
41
+ resolve_merge
42
+ commit_merge
43
+
44
+ exit 0
45
+ end
46
+
47
+ private
48
+
49
+ def resolve_merge
50
+ repo.index.load_for_update
51
+
52
+ merge = ::Merge::Resolve.new(repo, @inputs)
53
+ merge.on_progress { |info| puts info }
54
+ merge.execute
55
+
56
+ repo.index.write_updates
57
+ fail_on_conflict if repo.index.conflict?
58
+ end
59
+
60
+ def fail_on_conflict
61
+ edit_file(pending_commit.message_path) do |editor|
62
+ editor.puts(read_message || default_commit_message)
63
+ editor.puts("")
64
+ editor.note("Conflicts:")
65
+ repo.index.conflict_paths.each { |name| editor.note("\t#{ name }") }
66
+ editor.close
67
+ end
68
+
69
+ puts "Automatic merge failed; fix conflicts and then commit the result."
70
+ exit 1
71
+ end
72
+
73
+ def commit_merge
74
+ parents = [@inputs.left_oid, @inputs.right_oid]
75
+ message = compose_message
76
+
77
+ write_commit(parents, message)
78
+
79
+ pending_commit.clear
80
+ end
81
+
82
+ def compose_message
83
+ edit_file(pending_commit.message_path) do |editor|
84
+ editor.puts(read_message || default_commit_message)
85
+ editor.puts("")
86
+ editor.note(COMMIT_NOTES)
87
+
88
+ editor.close unless @options[:edit]
89
+ end
90
+ end
91
+
92
+ def default_commit_message
93
+ "Merge commit '#{ @inputs.right_name }'"
94
+ end
95
+
96
+ def handle_merged_ancestor
97
+ puts "Already up to date."
98
+ exit 0
99
+ end
100
+
101
+ def handle_fast_forward
102
+ a = repo.database.short_oid(@inputs.left_oid)
103
+ b = repo.database.short_oid(@inputs.right_oid)
104
+
105
+ puts "Updating #{ a }..#{ b }"
106
+ puts "Fast-forward"
107
+
108
+ repo.index.load_for_update
109
+
110
+ tree_diff = repo.database.tree_diff(@inputs.left_oid, @inputs.right_oid)
111
+ repo.migration(tree_diff).apply_changes
112
+
113
+ repo.index.write_updates
114
+ repo.refs.update_head(@inputs.right_oid)
115
+
116
+ exit 0
117
+ end
118
+
119
+ def handle_abort
120
+ repo.pending_commit.clear
121
+
122
+ repo.index.load_for_update
123
+ repo.hard_reset(repo.refs.read_head)
124
+ repo.index.write_updates
125
+
126
+ exit 0
127
+ rescue Repository::PendingCommit::Error => error
128
+ @stderr.puts "fatal: #{ error.message }"
129
+ exit 128
130
+ end
131
+
132
+ def handle_continue
133
+ repo.index.load
134
+ resume_merge(:merge)
135
+ rescue Repository::PendingCommit::Error => error
136
+ @stderr.puts "fatal: #{ error.message }"
137
+ exit 128
138
+ end
139
+
140
+ def handle_in_progress_merge
141
+ message = "Merging is not possible because you have unmerged files"
142
+ @stderr.puts "error: #{ message }."
143
+ @stderr.puts CONFLICT_MESSAGE
144
+ exit 128
145
+ end
146
+
147
+ end
148
+ end