grit 1.0.1 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of grit might be problematic. Click here for more details.
- data/History.txt +32 -2
- data/README.md +210 -0
- data/VERSION.yml +2 -2
- data/lib/grit.rb +11 -4
- data/lib/grit/blob.rb +11 -2
- data/lib/grit/commit.rb +41 -33
- data/lib/grit/commit_stats.rb +26 -2
- data/lib/grit/diff.rb +6 -6
- data/lib/grit/git-ruby.rb +1 -1
- data/lib/grit/git-ruby/git_object.rb +9 -3
- data/lib/grit/git-ruby/internal/{mmap.rb → file_window.rb} +2 -2
- data/lib/grit/git-ruby/internal/loose.rb +6 -6
- data/lib/grit/git-ruby/internal/pack.rb +19 -19
- data/lib/grit/git-ruby/object.rb +8 -2
- data/lib/grit/git-ruby/repository.rb +5 -6
- data/lib/grit/git.rb +14 -8
- data/lib/grit/index.rb +2 -2
- data/lib/grit/ref.rb +4 -6
- data/lib/grit/repo.rb +25 -3
- data/lib/grit/ruby1.9.rb +7 -0
- data/lib/grit/submodule.rb +5 -1
- data/lib/grit/tag.rb +61 -66
- data/lib/grit/tree.rb +20 -1
- data/test/dot_git/objects/b7/f932bd02b3e0a4228ee7b55832749028d345de +1 -0
- data/test/dot_git/packed-refs +2 -1
- data/test/dot_git/refs/tags/annotated +1 -0
- data/test/dot_git/refs/tags/not_annotated +1 -0
- data/test/fixtures/rev_list_delta_a +8 -0
- data/test/fixtures/rev_list_delta_b +11 -0
- data/test/fixtures/show_cc +637 -0
- data/test/helper.rb +1 -0
- data/test/test_blob.rb +18 -14
- data/test/test_commit.rb +65 -48
- data/test/test_git.rb +21 -1
- data/test/test_repo.rb +26 -0
- data/test/test_rubygit.rb +7 -2
- data/test/test_submodule.rb +18 -0
- data/test/test_tag.rb +53 -11
- data/test/test_tree.rb +5 -0
- metadata +11 -4
- data/README.txt +0 -222
data/lib/grit/repo.rb
CHANGED
@@ -209,6 +209,20 @@ module Grit
|
|
209
209
|
Commit.find_all(self, id, options).first
|
210
210
|
end
|
211
211
|
|
212
|
+
# Returns a list of commits that is in +other_repo+ but not in self
|
213
|
+
#
|
214
|
+
# Returns Grit::Commit[]
|
215
|
+
def commit_deltas_from(other_repo, ref = "master", other_ref = "master")
|
216
|
+
# TODO: we should be able to figure out the branch point, rather than
|
217
|
+
# rev-list'ing the whole thing
|
218
|
+
repo_refs = self.git.rev_list({}, ref).strip.split("\n")
|
219
|
+
other_repo_refs = other_repo.git.rev_list({}, other_ref).strip.split("\n")
|
220
|
+
|
221
|
+
(other_repo_refs - repo_refs).map do |ref|
|
222
|
+
Commit.find_all(other_repo, ref, {:max_count => 1}).first
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
212
226
|
# The Tree object for the given treeish reference
|
213
227
|
# +treeish+ is the reference (default 'master')
|
214
228
|
# +paths+ is an optional Array of directory paths to restrict the tree (deafult [])
|
@@ -324,11 +338,19 @@ module Grit
|
|
324
338
|
self.git.archive(options, treeish, "| gzip")
|
325
339
|
end
|
326
340
|
|
327
|
-
#
|
328
|
-
|
341
|
+
# Write an archive directly to a file
|
342
|
+
# +treeish+ is the treeish name/id (default 'master')
|
343
|
+
# +prefix+ is the optional prefix (default nil)
|
344
|
+
# +filename+ is the name of the file (default 'archive.tar.gz')
|
345
|
+
# +format+ is the optional format (default nil)
|
346
|
+
# +pipe+ is the command to run the output through (default 'gzip')
|
347
|
+
#
|
348
|
+
# Returns nothing
|
349
|
+
def archive_to_file(treeish = 'master', prefix = nil, filename = 'archive.tar.gz', format = nil, pipe = "gzip")
|
329
350
|
options = {}
|
330
351
|
options[:prefix] = prefix if prefix
|
331
|
-
|
352
|
+
options[:format] = format if format
|
353
|
+
self.git.archive(options, treeish, "| #{pipe} > #{filename}")
|
332
354
|
end
|
333
355
|
|
334
356
|
# Enable git-daemon serving of this repository by writing the
|
data/lib/grit/ruby1.9.rb
ADDED
data/lib/grit/submodule.rb
CHANGED
@@ -54,7 +54,7 @@ module Grit
|
|
54
54
|
blob = commit.tree/'.gitmodules'
|
55
55
|
return {} unless blob
|
56
56
|
|
57
|
-
lines = blob.data.split("\n")
|
57
|
+
lines = blob.data.gsub(/\r\n?/, "\n" ).split("\n")
|
58
58
|
|
59
59
|
config = {}
|
60
60
|
current = nil
|
@@ -75,6 +75,10 @@ module Grit
|
|
75
75
|
config
|
76
76
|
end
|
77
77
|
|
78
|
+
def basename
|
79
|
+
File.basename(name)
|
80
|
+
end
|
81
|
+
|
78
82
|
# Pretty object inspection
|
79
83
|
def inspect
|
80
84
|
%Q{#<Grit::Submodule "#{@id}">}
|
data/lib/grit/tag.rb
CHANGED
@@ -1,71 +1,66 @@
|
|
1
1
|
module Grit
|
2
|
-
|
3
|
-
class Tag
|
4
|
-
attr_reader :name
|
5
|
-
attr_reader :commit
|
6
|
-
|
7
|
-
# Instantiate a new Tag
|
8
|
-
# +name+ is the name of the head
|
9
|
-
# +commit+ is the Commit that the head points to
|
10
|
-
#
|
11
|
-
# Returns Grit::Tag (baked)
|
12
|
-
def initialize(name, commit)
|
13
|
-
@name = name
|
14
|
-
@commit = commit
|
15
|
-
end
|
16
|
-
|
17
|
-
# Find all Tags
|
18
|
-
# +repo+ is the Repo
|
19
|
-
# +options+ is a Hash of options
|
20
|
-
#
|
21
|
-
# Returns Grit::Tag[] (baked)
|
2
|
+
|
3
|
+
class Tag < Ref
|
22
4
|
def self.find_all(repo, options = {})
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
5
|
+
refs = []
|
6
|
+
already = {}
|
7
|
+
|
8
|
+
Dir.chdir(repo.path) do
|
9
|
+
files = Dir.glob(prefix + '/**/*')
|
10
|
+
|
11
|
+
files.each do |ref|
|
12
|
+
next if !File.file?(ref)
|
13
|
+
|
14
|
+
id = File.read(ref).chomp
|
15
|
+
name = ref.sub("#{prefix}/", '')
|
16
|
+
commit = commit_from_sha(repo, id)
|
17
|
+
|
18
|
+
if !already[name]
|
19
|
+
refs << self.new(name, commit)
|
20
|
+
already[name] = true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
if File.file?('packed-refs')
|
25
|
+
lines = File.readlines('packed-refs')
|
26
|
+
lines.each_with_index do |line, i|
|
27
|
+
if m = /^(\w{40}) (.*?)$/.match(line)
|
28
|
+
next if !Regexp.new('^' + prefix).match(m[2])
|
29
|
+
name = m[2].sub("#{prefix}/", '')
|
30
|
+
|
31
|
+
# Annotated tags in packed-refs include a reference
|
32
|
+
# to the commit object on the following line.
|
33
|
+
next_line = lines[i+1]
|
34
|
+
if next_line && next_line[0] == ?^
|
35
|
+
commit = Commit.create(repo, :id => next_line[1..-1].chomp)
|
36
|
+
else
|
37
|
+
commit = commit_from_sha(repo, m[1])
|
38
|
+
end
|
39
|
+
|
40
|
+
if !already[name]
|
41
|
+
refs << self.new(name, commit)
|
42
|
+
already[name] = true
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
43
47
|
end
|
44
|
-
|
45
|
-
|
48
|
+
|
49
|
+
refs
|
46
50
|
end
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
full_name, id = line.split("\0")
|
60
|
-
name = full_name.split("/").last
|
61
|
-
commit = Commit.create(repo, :id => id)
|
62
|
-
self.new(name, commit)
|
63
|
-
end
|
64
|
-
|
65
|
-
# Pretty object inspection
|
66
|
-
def inspect
|
67
|
-
%Q{#<Grit::Tag "#{@name}">}
|
51
|
+
|
52
|
+
def self.commit_from_sha(repo, id)
|
53
|
+
git_ruby_repo = GitRuby::Repository.new(repo.path)
|
54
|
+
object = git_ruby_repo.get_object_by_sha1(id)
|
55
|
+
|
56
|
+
if object.type == :commit
|
57
|
+
Commit.create(repo, :id => id)
|
58
|
+
elsif object.type == :tag
|
59
|
+
Commit.create(repo, :id => object.object)
|
60
|
+
else
|
61
|
+
raise "Unknown object type."
|
62
|
+
end
|
68
63
|
end
|
69
|
-
end
|
70
|
-
|
71
|
-
end
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
data/lib/grit/tree.rb
CHANGED
@@ -95,10 +95,29 @@ module Grit
|
|
95
95
|
end
|
96
96
|
end
|
97
97
|
|
98
|
+
def basename
|
99
|
+
File.basename(name)
|
100
|
+
end
|
101
|
+
|
98
102
|
# Pretty object inspection
|
99
103
|
def inspect
|
100
104
|
%Q{#<Grit::Tree "#{@id}">}
|
101
105
|
end
|
106
|
+
|
107
|
+
# Find only Tree objects from contents
|
108
|
+
def trees
|
109
|
+
contents.select {|v| v.kind_of? Tree}
|
110
|
+
end
|
111
|
+
|
112
|
+
# Find only Blob objects from contents
|
113
|
+
def blobs
|
114
|
+
contents.select {|v| v.kind_of? Blob}
|
115
|
+
end
|
116
|
+
|
117
|
+
# Compares trees by name
|
118
|
+
def <=>(other)
|
119
|
+
name <=> other.name
|
120
|
+
end
|
102
121
|
end # Tree
|
103
|
-
|
122
|
+
|
104
123
|
end # Grit
|
@@ -0,0 +1 @@
|
|
1
|
+
x5�1� @QkN�0�� P8����^H����ޤ���x��hͿb�0Ml&m��H�$d��cy4β��;Bh�,�d3��&,�W��s_Vxp]���p���o)C���D�dȒ�#:D�n6cP?��0�
|
data/test/dot_git/packed-refs
CHANGED
@@ -5,5 +5,6 @@ ca8a30f5a7f0f163bbe3b6f0abf18a6c83b0687a refs/heads/test/chacon
|
|
5
5
|
2d3acf90f35989df8f262dc50beadc4ee3ae1560 refs/heads/testing
|
6
6
|
ca8a30f5a7f0f163bbe3b6f0abf18a6c83b0687a refs/remotes/origin/master
|
7
7
|
2d3acf90f35989df8f262dc50beadc4ee3ae1560 refs/remotes/tom/master
|
8
|
-
f0055fda16c18fd8b27986dbf038c735b82198d7 refs/tags/
|
8
|
+
f0055fda16c18fd8b27986dbf038c735b82198d7 refs/tags/packed_annotated
|
9
9
|
^7bcc0ee821cdd133d8a53e8e7173a334fef448aa
|
10
|
+
ca8a30f5a7f0f163bbe3b6f0abf18a6c83b0687a refs/tags/packed
|
@@ -0,0 +1 @@
|
|
1
|
+
b7f932bd02b3e0a4228ee7b55832749028d345de
|
@@ -0,0 +1 @@
|
|
1
|
+
ca8a30f5a7f0f163bbe3b6f0abf18a6c83b0687a
|
@@ -0,0 +1,8 @@
|
|
1
|
+
e34590b7a2d186b3bb9a1170d02d52b36c791c78
|
2
|
+
8977833d74f8681aa0d9a5e84b0dd3d81519774d
|
3
|
+
6f5561530cb3a94e4c86454e84732197325be172
|
4
|
+
ee419e04a961543444be6db66aef52e6e37936d6
|
5
|
+
d845de9d438e1a249a0c2fcb778e8ea3b7e06cef
|
6
|
+
0bba4a6c10060405a94d52533af2f9bdacd4f29c
|
7
|
+
77711c0722964ead965e0ba2ee9ed4a03cb3d292
|
8
|
+
501d23cac6dd911511f15d091ee031a15b90ebde
|
@@ -0,0 +1,11 @@
|
|
1
|
+
4c8124ffcf4039d292442eeccabdeca5af5c5017
|
2
|
+
634396b2f541a9f2d58b00be1a07f0c358b999b3
|
3
|
+
ab25fd8483882c3bda8a458ad2965d2248654335
|
4
|
+
e34590b7a2d186b3bb9a1170d02d52b36c791c78
|
5
|
+
8977833d74f8681aa0d9a5e84b0dd3d81519774d
|
6
|
+
6f5561530cb3a94e4c86454e84732197325be172
|
7
|
+
ee419e04a961543444be6db66aef52e6e37936d6
|
8
|
+
d845de9d438e1a249a0c2fcb778e8ea3b7e06cef
|
9
|
+
0bba4a6c10060405a94d52533af2f9bdacd4f29c
|
10
|
+
77711c0722964ead965e0ba2ee9ed4a03cb3d292
|
11
|
+
501d23cac6dd911511f15d091ee031a15b90ebde
|
@@ -0,0 +1,637 @@
|
|
1
|
+
commit f8dd1f0b48e1106a62b47cc2927609ca589dc39a
|
2
|
+
tree 4c23a5137714e62c52f22e99b3104122868400ab
|
3
|
+
parent a0710955e70cbceef8cf805645a447f1b370b966
|
4
|
+
parent b724247612be9f55df7914cb86b64703810d7b73
|
5
|
+
author administrator <gmalamid@thoughtworks.com> 1224499981 +0100
|
6
|
+
committer administrator <gmalamid@thoughtworks.com> 1224499981 +0100
|
7
|
+
|
8
|
+
imported William Morgans utilities too, which are very very nice
|
9
|
+
|
10
|
+
diff --cc git-publish-branch
|
11
|
+
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..b28b4fde621dff1c72bcb570183bbe923e4a24b2
|
12
|
+
new file mode 100755
|
13
|
+
--- /dev/null
|
14
|
+
+++ b/git-publish-branch
|
15
|
+
@@@ -1,0 -1,0 +1,70 @@@
|
16
|
+
++#!/usr/bin/env ruby
|
17
|
+
++
|
18
|
+
++## git-publish-branch: a simple script to ease the unnecessarily complex
|
19
|
+
++## task of "publishing" a branch, i.e., taking a local branch, creating a
|
20
|
+
++## reference to it on a remote repo, and setting up the local branch to
|
21
|
+
++## track the remote one, all in one go. you can even delete that remote
|
22
|
+
++## reference.
|
23
|
+
++##
|
24
|
+
++## Usage: git publish-branch [-d] <branch> [repository]
|
25
|
+
++##
|
26
|
+
++## '-d' signifies deletion. <branch> is the branch to publish, and
|
27
|
+
++## [repository] defaults to "origin". The remote branch name will be the
|
28
|
+
++## same as the local branch name. Don't make life unnecessarily complex
|
29
|
+
++## for yourself.
|
30
|
+
++##
|
31
|
+
++## Note that unpublishing a branch doesn't delete the local branch.
|
32
|
+
++## Safety first!
|
33
|
+
++##
|
34
|
+
++## git-publish-branch Copyright 2008 William Morgan <wmorgan-git-wt-add@masanjin.net>.
|
35
|
+
++## This program is free software: you can redistribute it and/or modify
|
36
|
+
++## it under the terms of the GNU General Public License as published by
|
37
|
+
++## the Free Software Foundation, either version 3 of the License, or (at
|
38
|
+
++## your option) any later version.
|
39
|
+
++##
|
40
|
+
++## This program is distributed in the hope that it will be useful,
|
41
|
+
++## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
42
|
+
++## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
43
|
+
++## GNU General Public License for more details.
|
44
|
+
++##
|
45
|
+
++## You can find the GNU General Public License at:
|
46
|
+
++## http://www.gnu.org/licenses/
|
47
|
+
++
|
48
|
+
++def exec cmd
|
49
|
+
++ puts cmd
|
50
|
+
++ system cmd or die unless $fake
|
51
|
+
++end
|
52
|
+
++
|
53
|
+
++def die s=nil
|
54
|
+
++ $stderr.puts s if s
|
55
|
+
++ exit(-1)
|
56
|
+
++end
|
57
|
+
++
|
58
|
+
++head = `git symbolic-ref HEAD`.chomp.gsub(/refs\/heads\//, "")
|
59
|
+
++delete = ARGV.delete "-d"
|
60
|
+
++$fake = ARGV.delete "-n"
|
61
|
+
++branch = (ARGV.shift || head).gsub(/refs\/heads\//, "")
|
62
|
+
++remote = ARGV.shift || "origin"
|
63
|
+
++local_ref = `git show-ref heads/#{branch}`
|
64
|
+
++remote_ref = `git show-ref remotes/#{remote}/#{branch}`
|
65
|
+
++remote_config = `git config branch.#{branch}.merge`
|
66
|
+
++
|
67
|
+
++if delete
|
68
|
+
++ ## we don't do any checking here because the remote branch might actually
|
69
|
+
++ ## exist, whether we actually know about it or not.
|
70
|
+
++ exec "git push #{remote} :refs/heads/#{branch}"
|
71
|
+
++
|
72
|
+
++ unless local_ref.empty?
|
73
|
+
++ exec "git config --unset branch.#{branch}.remote"
|
74
|
+
++ exec "git config --unset branch.#{branch}.merge"
|
75
|
+
++ end
|
76
|
+
++else
|
77
|
+
++ die "No local branch #{branch} exists!" if local_ref.empty?
|
78
|
+
++ die "A remote branch #{branch} on #{remote} already exists!" unless remote_ref.empty?
|
79
|
+
++ die "Local branch #{branch} is already a tracking branch!" unless remote_config.empty?
|
80
|
+
++
|
81
|
+
++ exec "git push #{remote} #{branch}:refs/heads/#{branch}"
|
82
|
+
++ exec "git config branch.#{branch}.remote #{remote}"
|
83
|
+
++ exec "git config branch.#{branch}.merge refs/heads/#{branch}"
|
84
|
+
++end
|
85
|
+
++
|
86
|
+
diff --cc git-rank-contributors
|
87
|
+
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..3b272206d185e2f9b8089e6aaa00fde6acc308ef
|
88
|
+
new file mode 100755
|
89
|
+
--- /dev/null
|
90
|
+
+++ b/git-rank-contributors
|
91
|
+
@@@ -1,0 -1,0 +1,60 @@@
|
92
|
+
++#!/usr/bin/env ruby
|
93
|
+
++
|
94
|
+
++## git-rank-contributors: a simple script to trace through the logs and
|
95
|
+
++## rank contributors by the total size of the diffs they're responsible for.
|
96
|
+
++## A change counts twice as much as a plain addition or deletion.
|
97
|
+
++##
|
98
|
+
++## Output may or may not be suitable for inclusion in a CREDITS file.
|
99
|
+
++## Probably not without some editing, because people often commit from more
|
100
|
+
++## than one address.
|
101
|
+
++##
|
102
|
+
++## git-rank-contributors Copyright 2008 William Morgan <wmorgan-git-wt-add@masanjin.net>.
|
103
|
+
++## This program is free software: you can redistribute it and/or modify
|
104
|
+
++## it under the terms of the GNU General Public License as published by
|
105
|
+
++## the Free Software Foundation, either version 3 of the License, or (at
|
106
|
+
++## your option) any later version.
|
107
|
+
++##
|
108
|
+
++## This program is distributed in the hope that it will be useful,
|
109
|
+
++## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
110
|
+
++## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
111
|
+
++## GNU General Public License for more details.
|
112
|
+
++##
|
113
|
+
++## You can find the GNU General Public License at:
|
114
|
+
++## http://www.gnu.org/licenses/
|
115
|
+
++
|
116
|
+
++class String
|
117
|
+
++ def obfuscate; gsub(/@/, " at the ").gsub(/\.(\w+)(>|$)/, ' dot \1s\2') end
|
118
|
+
++ def htmlize; gsub("&", "&").gsub("<", "<").gsub(">", ">") end
|
119
|
+
++end
|
120
|
+
++
|
121
|
+
++lines = {}
|
122
|
+
++verbose = ARGV.delete("-v")
|
123
|
+
++obfuscate = ARGV.delete("-o")
|
124
|
+
++htmlize = ARGV.delete("-h")
|
125
|
+
++
|
126
|
+
++author = nil
|
127
|
+
++state = :pre_author
|
128
|
+
++`git log -M -C -C -p --no-color`.each do |l|
|
129
|
+
++ case
|
130
|
+
++ when (state == :pre_author || state == :post_author) && l =~ /Author: (.*)$/
|
131
|
+
++ author = $1
|
132
|
+
++ state = :post_author
|
133
|
+
++ lines[author] ||= 0
|
134
|
+
++ when state == :post_author && l =~ /^\+\+\+/
|
135
|
+
++ state = :in_diff
|
136
|
+
++ when state == :in_diff && l =~ /^[\+\-]/
|
137
|
+
++ lines[author] += 1
|
138
|
+
++ when state == :in_diff && l =~ /^commit /
|
139
|
+
++ state = :pre_author
|
140
|
+
++ end
|
141
|
+
++end
|
142
|
+
++
|
143
|
+
++lines.sort_by { |a, c| -c }.each do |a, c|
|
144
|
+
++ a = a.obfuscate if obfuscate
|
145
|
+
++ a = a.htmlize if htmlize
|
146
|
+
++ if verbose
|
147
|
+
++ puts "#{a}: #{c} lines of diff"
|
148
|
+
++ else
|
149
|
+
++ puts a
|
150
|
+
++ end
|
151
|
+
++end
|
152
|
+
diff --cc git-show-merges
|
153
|
+
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..12907502a6c6885e7183a0e71d9cfeed77917824
|
154
|
+
new file mode 100755
|
155
|
+
--- /dev/null
|
156
|
+
+++ b/git-show-merges
|
157
|
+
@@@ -1,0 -1,0 +1,49 @@@
|
158
|
+
++#!/usr/bin/env ruby
|
159
|
+
++
|
160
|
+
++## git-show-merges: a simple script to show you which topic branches have
|
161
|
+
++## been merged into the current branch, and which haven't. (Or, specify
|
162
|
+
++## the set of merge branches you're interested in on the command line.)
|
163
|
+
++##
|
164
|
+
++## git-show-merges Copyright 2008 William Morgan <wmorgan-git-wt-add@masanjin.net>.
|
165
|
+
++## This program is free software: you can redistribute it and/or modify
|
166
|
+
++## it under the terms of the GNU General Public License as published by
|
167
|
+
++## the Free Software Foundation, either version 3 of the License, or (at
|
168
|
+
++## your option) any later version.
|
169
|
+
++##
|
170
|
+
++## This program is distributed in the hope that it will be useful,
|
171
|
+
++## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
172
|
+
++## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
173
|
+
++## GNU General Public License for more details.
|
174
|
+
++##
|
175
|
+
++## You can find the GNU General Public License at:
|
176
|
+
++## http://www.gnu.org/licenses/
|
177
|
+
++heads = if ARGV.empty?
|
178
|
+
++ [`git symbolic-ref HEAD`.chomp]
|
179
|
+
++else
|
180
|
+
++ ARGV
|
181
|
+
++end.map { |r| r.gsub(/refs\/heads\//, "") }
|
182
|
+
++
|
183
|
+
++branches = `git show-ref --heads`.
|
184
|
+
++ scan(/^\S+ refs\/heads\/(\S+)$/).
|
185
|
+
++ map { |a| a.first }
|
186
|
+
++
|
187
|
+
++unknown = heads - branches
|
188
|
+
++unless unknown.empty?
|
189
|
+
++ $stderr.puts "Unknown branch: #{unknown.first}"
|
190
|
+
++ exit(-1)
|
191
|
+
++end
|
192
|
+
++
|
193
|
+
++branches -= heads
|
194
|
+
++
|
195
|
+
++heads.each do |h|
|
196
|
+
++ merged = branches.select { |b| `git log #{h}..#{b}` == "" }
|
197
|
+
++ unmerged = branches - merged
|
198
|
+
++
|
199
|
+
++ puts "merged into #{h}:"
|
200
|
+
++ merged.each { |b| puts " #{b}" }
|
201
|
+
++ puts
|
202
|
+
++ puts "not merged into #{h}: "
|
203
|
+
++ unmerged.each { |b| puts " #{b}" }
|
204
|
+
++
|
205
|
+
++ puts
|
206
|
+
++end
|
207
|
+
diff --cc git-wt-add
|
208
|
+
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..3125ab5495e5f2ab55ed48af5303031fae02c2ac
|
209
|
+
new file mode 100755
|
210
|
+
--- /dev/null
|
211
|
+
+++ b/git-wt-add
|
212
|
+
@@@ -1,0 -1,0 +1,196 @@@
|
213
|
+
++#!/usr/bin/env ruby
|
214
|
+
++
|
215
|
+
++## git-wt-add: A darcs-style interactive staging script for git. As the
|
216
|
+
++## name implies, git-wt-add walks you through unstaged changes on a
|
217
|
+
++## hunk-by-hunk basis and allows you to pick the ones you'd like staged.
|
218
|
+
++##
|
219
|
+
++## git-wt-add Copyright 2007 William Morgan <wmorgan-git-wt-add@masanjin.net>.
|
220
|
+
++## This program is free software: you can redistribute it and/or modify
|
221
|
+
++## it under the terms of the GNU General Public License as published by
|
222
|
+
++## the Free Software Foundation, either version 3 of the License, or
|
223
|
+
++## (at your option) any later version.
|
224
|
+
++##
|
225
|
+
++## This program is distributed in the hope that it will be useful,
|
226
|
+
++## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
227
|
+
++## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
228
|
+
++## GNU General Public License for more details.
|
229
|
+
++##
|
230
|
+
++## You can find the GNU General Public License at:
|
231
|
+
++## http://www.gnu.org/licenses/
|
232
|
+
++
|
233
|
+
++COLOR = /\e\[\d*m/
|
234
|
+
++
|
235
|
+
++class Hunk
|
236
|
+
++ attr_reader :file, :file_header, :diff
|
237
|
+
++ attr_accessor :disposition
|
238
|
+
++
|
239
|
+
++ def initialize file, file_header, diff
|
240
|
+
++ @file = file
|
241
|
+
++ @file_header = file_header
|
242
|
+
++ @diff = diff
|
243
|
+
++ @disposition = :unknown
|
244
|
+
++ end
|
245
|
+
++
|
246
|
+
++ def self.make_from diff
|
247
|
+
++ ret = []
|
248
|
+
++ state = :outside
|
249
|
+
++ file_header = hunk = file = nil
|
250
|
+
++
|
251
|
+
++ diff.each do |l| # a little state machine to parse git diff output
|
252
|
+
++ reprocess = false
|
253
|
+
++ begin
|
254
|
+
++ reprocess = false
|
255
|
+
++ case
|
256
|
+
++ when state == :outside && l =~ /^(#{COLOR})*diff --git a\/(.+) b\/(\2)/
|
257
|
+
++ file = $2
|
258
|
+
++ file_header = ""
|
259
|
+
++ when state == :outside && l =~ /^(#{COLOR})*index /
|
260
|
+
++ when state == :outside && l =~ /^(#{COLOR})*(---|\+\+\+) /
|
261
|
+
++ file_header += l + "\n"
|
262
|
+
++ when state == :outside && l =~ /^(#{COLOR})*@@ /
|
263
|
+
++ state = :in_hunk
|
264
|
+
++ hunk = l + "\n"
|
265
|
+
++ when state == :in_hunk && l =~ /^(#{COLOR})*(@@ |diff --git a)/
|
266
|
+
++ ret << Hunk.new(file, file_header, hunk)
|
267
|
+
++ state = :outside
|
268
|
+
++ reprocess = true
|
269
|
+
++ when state == :in_hunk
|
270
|
+
++ hunk += l + "\n"
|
271
|
+
++ else
|
272
|
+
++ raise "unparsable diff input: #{l.inspect}"
|
273
|
+
++ end
|
274
|
+
++ end while reprocess
|
275
|
+
++ end
|
276
|
+
++
|
277
|
+
++ ## add the final hunk
|
278
|
+
++ ret << Hunk.new(file, file_header, hunk) if hunk
|
279
|
+
++
|
280
|
+
++ ret
|
281
|
+
++ end
|
282
|
+
++end
|
283
|
+
++
|
284
|
+
++def help
|
285
|
+
++ puts <<EOS
|
286
|
+
++y: record this patch
|
287
|
+
++n: don't record it
|
288
|
+
++w: wait and decide later, defaulting to no
|
289
|
+
++
|
290
|
+
++s: don't record the rest of the changes to this file
|
291
|
+
++f: record the rest of the changes to this file
|
292
|
+
++
|
293
|
+
++d: record selected patches, skipping all the remaining patches
|
294
|
+
++a: record all the remaining patches
|
295
|
+
++q: cancel record
|
296
|
+
++
|
297
|
+
++j: skip to next patch
|
298
|
+
++k: back up to previous patch
|
299
|
+
++c: calculate number of patches
|
300
|
+
++h or ?: show this help
|
301
|
+
++
|
302
|
+
++<Space>: accept the current default (which is capitalized)
|
303
|
+
++EOS
|
304
|
+
++end
|
305
|
+
++
|
306
|
+
++def walk_through hunks
|
307
|
+
++ skip_files, record_files = {}, {}
|
308
|
+
++ skip_rest = record_rest = false
|
309
|
+
++
|
310
|
+
++ while hunks.any? { |h| h.disposition == :unknown }
|
311
|
+
++ pos = 0
|
312
|
+
++ until pos >= hunks.length
|
313
|
+
++ h = hunks[pos]
|
314
|
+
++ if h.disposition != :unknown
|
315
|
+
++ pos += 1
|
316
|
+
++ next
|
317
|
+
++ elsif skip_rest || skip_files[h.file]
|
318
|
+
++ h.disposition = :ignore
|
319
|
+
++ pos += 1
|
320
|
+
++ next
|
321
|
+
++ elsif record_rest || record_files[h.file]
|
322
|
+
++ h.disposition = :record
|
323
|
+
++ pos += 1
|
324
|
+
++ next
|
325
|
+
++ end
|
326
|
+
++
|
327
|
+
++ puts "Hunk from #{h.file}"
|
328
|
+
++ puts h.diff
|
329
|
+
++ print "Shall I stage this change? (#{pos + 1}/#{hunks.length}) [ynWsfqadk], or ? for help: "
|
330
|
+
++ c = $stdin.getc
|
331
|
+
++ puts
|
332
|
+
++ case c
|
333
|
+
++ when ?y: h.disposition = :record
|
334
|
+
++ when ?n: h.disposition = :ignore
|
335
|
+
++ when ?w, ?\ : h.disposition = :unknown
|
336
|
+
++ when ?s
|
337
|
+
++ h.disposition = :ignore
|
338
|
+
++ skip_files[h.file] = true
|
339
|
+
++ when ?f
|
340
|
+
++ h.disposition = :record
|
341
|
+
++ record_files[h.file] = true
|
342
|
+
++ when ?d: skip_rest = true
|
343
|
+
++ when ?a: record_rest = true
|
344
|
+
++ when ?q: exit
|
345
|
+
++ when ?k
|
346
|
+
++ if pos > 0
|
347
|
+
++ hunks[pos - 1].disposition = :unknown
|
348
|
+
++ pos -= 2 # double-bah
|
349
|
+
++ end
|
350
|
+
++ else
|
351
|
+
++ help
|
352
|
+
++ pos -= 1 # bah
|
353
|
+
++ end
|
354
|
+
++
|
355
|
+
++ pos += 1
|
356
|
+
++ puts
|
357
|
+
++ end
|
358
|
+
++ end
|
359
|
+
++end
|
360
|
+
++
|
361
|
+
++def make_patch hunks
|
362
|
+
++ patch = ""
|
363
|
+
++ did_header = {}
|
364
|
+
++ hunks.each do |h|
|
365
|
+
++ next unless h.disposition == :record
|
366
|
+
++ unless did_header[h.file]
|
367
|
+
++ patch += h.file_header
|
368
|
+
++ did_header[h.file] = true
|
369
|
+
++ end
|
370
|
+
++ patch += h.diff
|
371
|
+
++ end
|
372
|
+
++
|
373
|
+
++ patch.gsub COLOR, ""
|
374
|
+
++end
|
375
|
+
++
|
376
|
+
++### execution starts here ###
|
377
|
+
++
|
378
|
+
++diff = `git diff`.split(/\r?\n/)
|
379
|
+
++if diff.empty?
|
380
|
+
++ puts "No unstaged changes."
|
381
|
+
++ exit
|
382
|
+
++end
|
383
|
+
++hunks = Hunk.make_from diff
|
384
|
+
++
|
385
|
+
++## unix-centric!
|
386
|
+
++state = `stty -g`
|
387
|
+
++begin
|
388
|
+
++ `stty -icanon` # immediate keypress mode
|
389
|
+
++ walk_through hunks
|
390
|
+
++ensure
|
391
|
+
++ `stty #{state}`
|
392
|
+
++end
|
393
|
+
++
|
394
|
+
++patch = make_patch hunks
|
395
|
+
++if patch.empty?
|
396
|
+
++ puts "No changes selected for staging."
|
397
|
+
++else
|
398
|
+
++ IO.popen("git apply --cached", "w") { |f| f.puts patch }
|
399
|
+
++ puts <<EOS
|
400
|
+
++Staged patch of #{patch.split("\n").size} lines.
|
401
|
+
++
|
402
|
+
++Possible next commands:
|
403
|
+
++ git diff --cached: see staged changes
|
404
|
+
++ git commit: commit staged changes
|
405
|
+
++ git reset: unstage changes
|
406
|
+
++EOS
|
407
|
+
++end
|
408
|
+
++
|
409
|
+
diff --cc git-wtf
|
410
|
+
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..ea4b27f6812cf9daf84b047bff4443cc554f92bf
|
411
|
+
new file mode 100755
|
412
|
+
--- /dev/null
|
413
|
+
+++ b/git-wtf
|
414
|
+
@@@ -1,0 -1,0 +1,223 @@@
|
415
|
+
++#!/usr/bin/env ruby
|
416
|
+
++
|
417
|
+
++## git-wtf: display the state of your repository in a readable and easy-to-scan
|
418
|
+
++## format.
|
419
|
+
++##
|
420
|
+
++## git-wtf tries to ease the task of having many git branches. It's also useful
|
421
|
+
++## for getting a summary of how tracking branches relate to a remote server.
|
422
|
+
++##
|
423
|
+
++## git-wtf shows you:
|
424
|
+
++## - How your branch relates to the remote repo, if it's a tracking branch.
|
425
|
+
++## - How your branch relates to non-feature ("version") branches, if it's a
|
426
|
+
++## feature branch.
|
427
|
+
++## - How your branch relates to the feature branches, if it's a version branch.
|
428
|
+
++##
|
429
|
+
++## For each of these relationships, git-wtf displays the commits pending on
|
430
|
+
++## either side, if any. It displays checkboxes along the side for easy scanning
|
431
|
+
++## of merged/non-merged branches.
|
432
|
+
++##
|
433
|
+
++## If you're working against a remote repo, git-wtf is best used between a 'git
|
434
|
+
++## fetch' and a 'git merge' (or 'git pull' if you don't mind the redundant
|
435
|
+
++## network access).
|
436
|
+
++##
|
437
|
+
++## Usage: git wtf [branch+] [-l|--long] [-a|--all] [--dump-config]
|
438
|
+
++##
|
439
|
+
++## If [branch] is not specified, git-wtf will use the current branch. With
|
440
|
+
++## --long, you'll see author info and date for each commit. With --all, you'll
|
441
|
+
++## see all commits, not just the first 5. With --dump-config, git-wtf will
|
442
|
+
++## print out its current configuration in YAML format and exit.
|
443
|
+
++##
|
444
|
+
++## git-wtf uses some heuristics to determine which branches are version
|
445
|
+
++## branches, and which are feature branches. (Specifically, it assumes the
|
446
|
+
++## version branches are named "master", "next" and "edge".) If it guesses
|
447
|
+
++## incorrectly, you will have to create a .git-wtfrc file.
|
448
|
+
++##
|
449
|
+
++## git-wtf looks for a .git-wtfrc file starting in the current directory, and
|
450
|
+
++## recursively up to the root. The config file is a YAML file that specifies
|
451
|
+
++## the version branches, any branches to ignore, and the max number of commits
|
452
|
+
++## to display when --all isn't used. To start building a configuration file,
|
453
|
+
++## run "git-wtf --dump-config > .git-wtfrc" and edit it.
|
454
|
+
++##
|
455
|
+
++## IMPORTANT NOTE: all local branches referenced in .git-wtfrc must be prefixed
|
456
|
+
++## with heads/, e.g. "heads/master". Remote branches must be of the form
|
457
|
+
++## remotes/<remote>/<branch>.
|
458
|
+
++##
|
459
|
+
++## git-wtf Copyright 2008 William Morgan <wmorgan-git-wt-add@masanjin.net>.
|
460
|
+
++## This program is free software: you can redistribute it and/or modify it
|
461
|
+
++## under the terms of the GNU General Public License as published by the Free
|
462
|
+
++## Software Foundation, either version 3 of the License, or (at your option)
|
463
|
+
++## any later version.
|
464
|
+
++##
|
465
|
+
++## This program is distributed in the hope that it will be useful, but WITHOUT
|
466
|
+
++## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
467
|
+
++## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
468
|
+
++## more details.
|
469
|
+
++##
|
470
|
+
++## You can find the GNU General Public License at: http://www.gnu.org/licenses/
|
471
|
+
++
|
472
|
+
++
|
473
|
+
++require 'yaml'
|
474
|
+
++CONFIG_FN = ".git-wtfrc"
|
475
|
+
++
|
476
|
+
++class Numeric; def pluralize s; "#{to_s} #{s}" + (self != 1 ? "s" : "") end end
|
477
|
+
++
|
478
|
+
++$long = ARGV.delete("--long") || ARGV.delete("-l")
|
479
|
+
++$all = ARGV.delete("--all") || ARGV.delete("-a")
|
480
|
+
++$dump_config = ARGV.delete("--dump-config")
|
481
|
+
++
|
482
|
+
++## find config file
|
483
|
+
++$config = { "versions" => %w(heads/master heads/next heads/edge), "ignore" => [], "max_commits" => 5 }.merge begin
|
484
|
+
++ p = File.expand_path "."
|
485
|
+
++ fn = while true
|
486
|
+
++ fn = File.join p, CONFIG_FN
|
487
|
+
++ break fn if File.exist? fn
|
488
|
+
++ pp = File.expand_path File.join(p, "..")
|
489
|
+
++ break if p == pp
|
490
|
+
++ p = pp
|
491
|
+
++ end
|
492
|
+
++
|
493
|
+
++ (fn && YAML::load_file(fn)) || {} # YAML turns empty files into false
|
494
|
+
++end
|
495
|
+
++
|
496
|
+
++if $dump_config
|
497
|
+
++ puts $config.to_yaml
|
498
|
+
++ exit(0)
|
499
|
+
++end
|
500
|
+
++
|
501
|
+
++## the set of commits in 'to' that aren't in 'from'.
|
502
|
+
++## if empty, 'to' has been merged into 'from'.
|
503
|
+
++def commits_between from, to
|
504
|
+
++ if $long
|
505
|
+
++ `git log --pretty=format:"- %s [%h] (%ae; %ar)" #{from}..#{to}`
|
506
|
+
++ else
|
507
|
+
++ `git log --pretty=format:"- %s [%h]" #{from}..#{to}`
|
508
|
+
++ end.split(/[\r\n]+/)
|
509
|
+
++end
|
510
|
+
++
|
511
|
+
++def show_commits commits, prefix=" "
|
512
|
+
++ if commits.empty?
|
513
|
+
++ puts "#{prefix} none"
|
514
|
+
++ else
|
515
|
+
++ max = $all ? commits.size : $config["max_commits"]
|
516
|
+
++ max -= 1 if max == commits.size - 1 # never show "and 1 more"
|
517
|
+
++ commits[0 ... max].each { |c| puts "#{prefix}#{c}" }
|
518
|
+
++ puts "#{prefix}... and #{commits.size - max} more." if commits.size > max
|
519
|
+
++ end
|
520
|
+
++end
|
521
|
+
++
|
522
|
+
++def ahead_behind_string ahead, behind
|
523
|
+
++ [ahead.empty? ? nil : "#{ahead.size.pluralize 'commit'} ahead",
|
524
|
+
++ behind.empty? ? nil : "#{behind.size.pluralize 'commit'} behind"].
|
525
|
+
++ compact.join("; ")
|
526
|
+
++end
|
527
|
+
++
|
528
|
+
++def show b, all_branches
|
529
|
+
++ puts "Local branch: #{b[:local_branch]}"
|
530
|
+
++ both = false
|
531
|
+
++
|
532
|
+
++ if b[:remote_branch]
|
533
|
+
++ pushc = commits_between b[:remote_branch], b[:local_branch]
|
534
|
+
++ pullc = commits_between b[:local_branch], b[:remote_branch]
|
535
|
+
++
|
536
|
+
++ both = !pushc.empty? && !pullc.empty?
|
537
|
+
++ if pushc.empty?
|
538
|
+
++ puts "[x] in sync with remote"
|
539
|
+
++ else
|
540
|
+
++ action = both ? "push after rebase / merge" : "push"
|
541
|
+
++ puts "[ ] NOT in sync with remote (needs #{action})"
|
542
|
+
++ show_commits pushc
|
543
|
+
++ end
|
544
|
+
++
|
545
|
+
++ puts "\nRemote branch: #{b[:remote_branch]} (#{b[:remote_url]})"
|
546
|
+
++
|
547
|
+
++ if pullc.empty?
|
548
|
+
++ puts "[x] in sync with local"
|
549
|
+
++ else
|
550
|
+
++ action = pushc.empty? ? "merge" : "rebase / merge"
|
551
|
+
++ puts "[ ] NOT in sync with local (needs #{action})"
|
552
|
+
++ show_commits pullc
|
553
|
+
++
|
554
|
+
++ both = !pushc.empty? && !pullc.empty?
|
555
|
+
++ end
|
556
|
+
++ end
|
557
|
+
++
|
558
|
+
++ vbs, fbs = all_branches.partition { |name, br| $config["versions"].include? br[:local_branch] }
|
559
|
+
++ if $config["versions"].include? b[:local_branch]
|
560
|
+
++ puts "\nFeature branches:" unless fbs.empty?
|
561
|
+
++ fbs.each do |name, br|
|
562
|
+
++ remote_ahead = b[:remote_branch] ? commits_between(b[:remote_branch], br[:local_branch]) : []
|
563
|
+
++ local_ahead = commits_between b[:local_branch], br[:local_branch]
|
564
|
+
++ if local_ahead.empty? && remote_ahead.empty?
|
565
|
+
++ puts "[x] #{br[:name]} is merged in"
|
566
|
+
++ elsif local_ahead.empty? && b[:remote_branch]
|
567
|
+
++ puts "(x) #{br[:name]} merged in (only locally)"
|
568
|
+
++ else
|
569
|
+
++ behind = commits_between br[:local_branch], b[:local_branch]
|
570
|
+
++ puts "[ ] #{br[:name]} is NOT merged in (#{ahead_behind_string local_ahead, behind})"
|
571
|
+
++ show_commits local_ahead
|
572
|
+
++ end
|
573
|
+
++ end
|
574
|
+
++ else
|
575
|
+
++ puts "\nVersion branches:" unless vbs.empty? # unlikely
|
576
|
+
++ vbs.each do |v, br|
|
577
|
+
++ ahead = commits_between v, b[:local_branch]
|
578
|
+
++ if ahead.empty?
|
579
|
+
++ puts "[x] merged into #{v}"
|
580
|
+
++ else
|
581
|
+
++ #behind = commits_between b[:local_branch], v
|
582
|
+
++ puts "[ ] NOT merged into #{v} (#{ahead.size.pluralize 'commit'} ahead)"
|
583
|
+
++ show_commits ahead
|
584
|
+
++ end
|
585
|
+
++ end
|
586
|
+
++ end
|
587
|
+
++
|
588
|
+
++ puts "\nWARNING: local and remote branches have diverged. A merge will occur unless you rebase." if both
|
589
|
+
++end
|
590
|
+
++
|
591
|
+
++branches = `git show-ref`.inject({}) do |hash, l|
|
592
|
+
++ sha1, ref = l.chomp.split " refs/"
|
593
|
+
++ next hash if $config["ignore"].member? ref
|
594
|
+
++ next hash unless ref =~ /^heads\/(.+)/
|
595
|
+
++ name = $1
|
596
|
+
++ hash[name] = { :name => name, :local_branch => ref }
|
597
|
+
++ hash
|
598
|
+
++end
|
599
|
+
++
|
600
|
+
++remotes = `git config --get-regexp ^remote\.\*\.url`.inject({}) do |hash, l|
|
601
|
+
++ l =~ /^remote\.(.+?)\.url (.+)$/ or next hash
|
602
|
+
++ hash[$1] ||= $2
|
603
|
+
++ hash
|
604
|
+
++end
|
605
|
+
++
|
606
|
+
++`git config --get-regexp ^branch\.`.each do |l|
|
607
|
+
++ case l
|
608
|
+
++ when /branch\.(.*?)\.remote (.+)/
|
609
|
+
++ branches[$1] ||= {}
|
610
|
+
++ branches[$1][:remote] = $2
|
611
|
+
++ branches[$1][:remote_url] = remotes[$2]
|
612
|
+
++ when /branch\.(.*?)\.merge ((refs\/)?heads\/)?(.+)/
|
613
|
+
++ branches[$1] ||= {}
|
614
|
+
++ branches[$1][:remote_mergepoint] = $4
|
615
|
+
++ end
|
616
|
+
++end
|
617
|
+
++
|
618
|
+
++branches.each { |k, v| v[:remote_branch] = "#{v[:remote]}/#{v[:remote_mergepoint]}" if v[:remote] && v[:remote_mergepoint] }
|
619
|
+
++
|
620
|
+
++show_dirty = ARGV.empty?
|
621
|
+
++targets = if ARGV.empty?
|
622
|
+
++ [`git symbolic-ref HEAD`.chomp.sub(/^refs\/heads\//, "")]
|
623
|
+
++else
|
624
|
+
++ ARGV
|
625
|
+
++end.map { |t| branches[t] or abort "Error: can't find branch #{t.inspect}." }
|
626
|
+
++
|
627
|
+
++targets.each { |t| show t, branches }
|
628
|
+
++
|
629
|
+
++modified = show_dirty && `git ls-files -m` != ""
|
630
|
+
++uncommitted = show_dirty && `git diff-index --cached HEAD` != ""
|
631
|
+
++
|
632
|
+
++puts if modified || uncommitted
|
633
|
+
++puts "NOTE: working directory contains modified files" if modified
|
634
|
+
++puts "NOTE: staging area contains staged but uncommitted files" if uncommitted
|
635
|
+
++
|
636
|
+
++# the end!
|
637
|
+
++
|