git-trac 0.0.20080206 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README +39 -24
- data/Rakefile +3 -2
- data/lib/git/trac.rb +1 -0
- data/lib/git/trac/attachment.rb +17 -11
- data/lib/git/trac/pager.rb +33 -11
- data/lib/git/trac/repository.rb +54 -3
- data/lib/git/trac/runner.rb +139 -65
- data/lib/git/trac/runner/apply.rb +14 -18
- data/lib/git/trac/runner/checkout.rb +81 -0
- data/lib/git/trac/runner/cleanup.rb +18 -9
- data/lib/git/trac/runner/fetch.rb +34 -28
- data/lib/git/trac/runner/help.rb +141 -0
- data/lib/git/trac/runner/push.rb +79 -0
- data/lib/git/trac/runner/show.rb +55 -8
- data/lib/git/trac/ticket.rb +14 -48
- data/lib/git/trac/version.rb +11 -0
- metadata +14 -12
- data/lib/git/trac/runner/download.rb +0 -44
- data/lib/git/trac/runner/upload_patch.rb +0 -62
@@ -4,8 +4,12 @@ module Git
|
|
4
4
|
|
5
5
|
class Apply < Base #:nodoc:
|
6
6
|
|
7
|
+
def self.summary
|
8
|
+
"Apply a patch directly to the work tree"
|
9
|
+
end
|
10
|
+
|
7
11
|
def banner_arguments
|
8
|
-
"[
|
12
|
+
"[<attachment>]"
|
9
13
|
end
|
10
14
|
|
11
15
|
def description
|
@@ -14,43 +18,35 @@ Apply a patch directly to the work tree. This command is a building block used
|
|
14
18
|
by other, more powerful commands and most users will never need to invoke it
|
15
19
|
directly.
|
16
20
|
|
17
|
-
The argument can be either a "ticket/filename" specifiying the patch to apply,
|
18
|
-
or simply ticket, in which case the tickets's last patch is applied. If no
|
19
|
-
argument is given, the patch is read from stdin.
|
20
|
-
|
21
21
|
The --depth option specifies how many directories deep to recursively attempt
|
22
22
|
to apply the patch in. For a depth of 2, the patch will be attempted in the
|
23
|
-
repositority root, foo, foo/bar, but not foo/baz. It will also try
|
24
|
-
off up to 2 directories off of the paths inside the patch, the same
|
25
|
-
-p1, and -p2 with patch(1). The option is ignored if the patch appears
|
26
|
-
been generated with git. The default value is 0, but this can be
|
27
|
-
the trac.depth configuration option.
|
23
|
+
repositority root, foo, foo/bar, but not foo/bar/baz. It will also try
|
24
|
+
stripping off up to 2 directories off of the paths inside the patch, the same
|
25
|
+
as -p0, -p1, and -p2 with patch(1). The option is ignored if the patch appears
|
26
|
+
to have been generated with git. The default value is 0, but this can be
|
27
|
+
changed with the trac.depth configuration option.
|
28
28
|
|
29
29
|
If the patch fails to apply even after a depth search, and the patch contains
|
30
30
|
carriage returns, a second pass is made with those carriage returns stripped.
|
31
|
+
|
32
|
+
If no argument is given, the patch is read from stdin.
|
31
33
|
EOF
|
32
34
|
end
|
33
35
|
|
34
36
|
def add_options(opts)
|
35
|
-
require_ticket_number
|
36
37
|
opts.on("--depth NUM","search NUM directories deep for a root") do |n|
|
37
38
|
options[:depth] = n
|
38
39
|
end
|
39
40
|
opts.on("--root DIR","apply patches relative to DIR") do |dir|
|
40
41
|
options[:root] = dir
|
41
42
|
end
|
42
|
-
add_local_option(opts)
|
43
43
|
end
|
44
44
|
|
45
45
|
def run
|
46
46
|
if @argv.empty?
|
47
|
-
patch = Patch.new(
|
48
|
-
elsif @argv.size > 1
|
49
|
-
abort "too many arguments"
|
47
|
+
patch = Patch.new(repository,$stdin.read)
|
50
48
|
else
|
51
|
-
|
52
|
-
patch = @repository.ticket(number).attachment(filename).patch
|
53
|
-
end
|
49
|
+
patch = one_attachment.patch
|
54
50
|
end
|
55
51
|
abort "no changes" if patch.body.empty?
|
56
52
|
unless patch.apply(options)
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module Git
|
2
|
+
module Trac
|
3
|
+
class Runner
|
4
|
+
|
5
|
+
class Checkout < Base #:nodoc:
|
6
|
+
|
7
|
+
include Fetchable
|
8
|
+
|
9
|
+
def self.summary
|
10
|
+
"Fetch an attachment and check it out into a branch"
|
11
|
+
end
|
12
|
+
|
13
|
+
def banner_arguments
|
14
|
+
"[options] <attachment> [<branchname>]"
|
15
|
+
end
|
16
|
+
|
17
|
+
def description
|
18
|
+
<<-EOF
|
19
|
+
Fetch the specified attachment and check it out into a new branch. If no
|
20
|
+
branch name is given, one is generated automatically from the attachment
|
21
|
+
filename.
|
22
|
+
|
23
|
+
If the branch already exists, it will be deleted if it contains either the same
|
24
|
+
exact changes as the attachment, or no changes at all, with respect to
|
25
|
+
upstream.
|
26
|
+
EOF
|
27
|
+
end
|
28
|
+
|
29
|
+
def add_options(opts)
|
30
|
+
@checkout_options = []
|
31
|
+
super
|
32
|
+
opts.on("--rebase","rebase after checkout") do
|
33
|
+
options[:rebase] = true
|
34
|
+
end
|
35
|
+
opts.on("-q","git checkout -q") do
|
36
|
+
@checkout_options << "-f"
|
37
|
+
end
|
38
|
+
opts.on("-f","git checkout -f") do
|
39
|
+
@checkout_options << "-f"
|
40
|
+
end
|
41
|
+
opts.on("-m","git checkout -m") do
|
42
|
+
@checkout_options << "-m"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def run
|
47
|
+
options[:upstream] ||= repository.guess_upstream || "refs/remotes/trunk"
|
48
|
+
fetch_unless_local
|
49
|
+
attachment = one_attachment(false)
|
50
|
+
branch = @argv.shift || attachment.name
|
51
|
+
too_many_arguments if @argv.any?
|
52
|
+
fetch_or_abort(attachment)
|
53
|
+
|
54
|
+
to_be_deleted = nil
|
55
|
+
repository.each_ref("refs/heads/#{branch}") do |object,ref|
|
56
|
+
hash = repository.diff_hash(object)
|
57
|
+
if hash == Digest::SHA1.digest("") || hash == repository.diff_hash(attachment.tag_name)
|
58
|
+
to_be_deleted = "#{branch}_tmp_git-trac_#{$$}"
|
59
|
+
repository.exec("git","branch","-m",branch,to_be_deleted)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
repository.in_work_tree do
|
64
|
+
unless Kernel.system("git","checkout",*@checkout_options + ["-b",branch,attachment.tag_name])
|
65
|
+
exitstatus = $?.exitstatus
|
66
|
+
if to_be_deleted
|
67
|
+
repository.exec("git","branch","-m",to_be_deleted,branch)
|
68
|
+
end
|
69
|
+
exit exitstatus
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
repository.exec("git","branch","-D",to_be_deleted) if to_be_deleted
|
74
|
+
system("git","rebase",options[:upstream]) if options[:rebase]
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -4,13 +4,17 @@ module Git
|
|
4
4
|
|
5
5
|
class Cleanup < Base #:nodoc:
|
6
6
|
|
7
|
+
def self.summary
|
8
|
+
"Remove old branches for a ticket"
|
9
|
+
end
|
10
|
+
|
7
11
|
def banner_arguments
|
8
|
-
"[options] [
|
12
|
+
"[options] [<attachment>...]"
|
9
13
|
end
|
10
14
|
|
11
15
|
def description
|
12
16
|
<<-EOF
|
13
|
-
Remove remote heads for a given ticket (e.g., trac/12345/
|
17
|
+
Remove remote heads for a given ticket (e.g., trac/12345/work.patch). Also
|
14
18
|
removes branches that point to one of these heads. Branches that have been
|
15
19
|
committed to will not be removed. The default is to target tickets that have
|
16
20
|
been closed, but you can also specify ticket numbers explicitly or use --all.
|
@@ -18,22 +22,27 @@ been closed, but you can also specify ticket numbers explicitly or use --all.
|
|
18
22
|
end
|
19
23
|
|
20
24
|
def add_options(opts)
|
21
|
-
opts.separator("Options:")
|
22
25
|
opts.on("-a","--all", "cleanup all tickets") { options[:all] = true }
|
26
|
+
opts.on("--only-branches", "cleanup branches only, not remote heads") do |b|
|
27
|
+
options[:only_branches] = true
|
28
|
+
end
|
29
|
+
opts.on("--[no-]rebased","also cleanup rebased attachments") do |b|
|
30
|
+
options[:rebased] = b
|
31
|
+
end
|
23
32
|
end
|
24
33
|
|
25
34
|
def run
|
26
35
|
if options[:all]
|
27
|
-
|
28
|
-
t.cleanup
|
36
|
+
repository.working_tickets.each do |t|
|
37
|
+
t.cleanup(options)
|
29
38
|
end
|
30
39
|
elsif @argv.any?
|
31
|
-
|
32
|
-
|
40
|
+
each_ticket_or_attachment do |ticket_or_attachment|
|
41
|
+
ticket_or_attachment.cleanup(options)
|
33
42
|
end
|
34
43
|
else
|
35
|
-
|
36
|
-
t.cleanup unless t.open?
|
44
|
+
repository.working_tickets.each do |t|
|
45
|
+
t.cleanup(options) unless t.open?
|
37
46
|
end
|
38
47
|
end
|
39
48
|
end
|
@@ -4,54 +4,60 @@ module Git
|
|
4
4
|
|
5
5
|
class Fetch < Base #:nodoc:
|
6
6
|
|
7
|
+
include Fetchable
|
8
|
+
|
9
|
+
def self.summary
|
10
|
+
"Create remote heads for ticket attachments"
|
11
|
+
end
|
12
|
+
|
7
13
|
def banner_arguments
|
8
|
-
"
|
14
|
+
"<attachment>..."
|
9
15
|
end
|
10
16
|
|
11
17
|
def description
|
12
18
|
<<-EOF
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
there is an implied `git cleanup
|
21
|
-
potentially remove conflicting branches first.
|
22
|
-
|
19
|
+
For each attachment argument, find the revision that most recently preceeds the
|
20
|
+
the time of upload, apply the patch to it, create a new commit, and add a
|
21
|
+
remote head of the form refs/remotes/trac/ticketnumber/filename.ext.
|
22
|
+
|
23
|
+
If the --auto-branch option is given, a unique branch is created for each
|
24
|
+
unique base name (filename without extension) of each attachment. If multiple
|
25
|
+
attachments have the same base name, the newest is used. Existing branches
|
26
|
+
will not be overridden, but there is an implied `git cleanup` that runs
|
27
|
+
beforehand which could potentially remove conflicting branches first.
|
28
|
+
Automatic branch creation used to be the default, but now the behavior is
|
29
|
+
deprecated and may be removed in a future release. Consider using `git-trac
|
30
|
+
checkout` instead.
|
23
31
|
EOF
|
24
32
|
end
|
25
33
|
|
26
34
|
def add_options(opts)
|
27
|
-
|
28
|
-
opts.on("--branch
|
29
|
-
options[:
|
30
|
-
end
|
31
|
-
opts.on("--depth NUM","search depth (see git-trac help apply)") do |n|
|
32
|
-
options[:depth] = n
|
35
|
+
super
|
36
|
+
opts.on("--auto-branch","create branches for attachments") do
|
37
|
+
options[:auto_branch] = true
|
33
38
|
end
|
34
|
-
opts.on("--root DIR","apply patches relative to DIR") do |dir|
|
35
|
-
options[:root] = dir
|
36
|
-
end
|
37
|
-
add_local_option(opts)
|
38
39
|
end
|
39
40
|
|
40
41
|
def run
|
41
42
|
fetch_unless_local
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
end
|
47
|
-
@repository.ticket(number).fetch(loop_opts) do |attachment, dir|
|
48
|
-
if dir == "."
|
43
|
+
each_ticket_or_attachment do |ticket|
|
44
|
+
seen = {}
|
45
|
+
ticket.fetch(options) do |attachment, dir, commit|
|
46
|
+
if dir == "." || dir == true
|
49
47
|
puts "#{attachment.tag_name}"
|
50
48
|
elsif dir
|
51
49
|
puts "#{attachment.tag_name} (#{dir})"
|
52
50
|
else
|
53
51
|
$stderr.puts "#{attachment.tag_name} FAILED"
|
54
52
|
end
|
53
|
+
seen[attachment.name] = commit if commit
|
54
|
+
end
|
55
|
+
if options[:auto_branch]
|
56
|
+
seen.each do |k,v|
|
57
|
+
if !File.exists?(path = "#{repository.git_dir}/refs/heads/#{k}")
|
58
|
+
File.open(path, "w") {|f| f.puts v}
|
59
|
+
end
|
60
|
+
end
|
55
61
|
end
|
56
62
|
end
|
57
63
|
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
module Git
|
2
|
+
module Trac
|
3
|
+
class Runner
|
4
|
+
|
5
|
+
class Help < Base #:nodoc:
|
6
|
+
|
7
|
+
def self.summary
|
8
|
+
"Help for a command or topic"
|
9
|
+
end
|
10
|
+
|
11
|
+
def banner_arguments
|
12
|
+
"[<command> | --commands | <topic> | --topics]"
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_options(opts)
|
16
|
+
@options = {} # Don't try to read the repository config
|
17
|
+
opts.on("--commands","list all commands") do |commands|
|
18
|
+
options[:commands] = commands
|
19
|
+
end
|
20
|
+
opts.on("--topics","list all topics") do |topics|
|
21
|
+
options[:topics] = topics
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def description
|
26
|
+
<<-EOF
|
27
|
+
Show help for a given topic or command.
|
28
|
+
EOF
|
29
|
+
end
|
30
|
+
|
31
|
+
def commands
|
32
|
+
Runner.commands.map do |runner|
|
33
|
+
[runner.command, runner.summary]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def topics
|
38
|
+
TOPICS.keys.compact.sort.map do |key|
|
39
|
+
[key, TOPICS[key].first]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def run
|
44
|
+
topic = @argv.first
|
45
|
+
if options[:commands]
|
46
|
+
puts commands.map {|t| "%-15s %s\n" % t}.join
|
47
|
+
elsif options[:topics]
|
48
|
+
puts topics.map {|t| "%-15s %s\n" % t}.join
|
49
|
+
elsif !topic
|
50
|
+
Pager.new.page(default)
|
51
|
+
elsif klass = Runner.for_command(topic)
|
52
|
+
return klass.new(["--help"],nil)
|
53
|
+
elsif TOPICS[topic]
|
54
|
+
body = TOPICS[topic].last
|
55
|
+
body = body.call(self) if body.respond_to?(:call)
|
56
|
+
Pager.new.page(body)
|
57
|
+
else
|
58
|
+
abort "no such topic #{topic}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def default
|
63
|
+
out = "Usage: git-trac <command> [options] [arguments]\n"
|
64
|
+
out << "\nCommands:\n"
|
65
|
+
out << commands.map {|t| " %-15s %s\n" % t}.join
|
66
|
+
out << "\nHelp topics:\n"
|
67
|
+
out << topics.map {|t| " %-15s %s\n" % t}.join
|
68
|
+
out << "\nUse git-trac help <command> or git-trac help <topic> for further information.\n"
|
69
|
+
end
|
70
|
+
|
71
|
+
TOPICS = {}
|
72
|
+
|
73
|
+
TOPICS["config"] = ["Configuration"] << <<-EOF
|
74
|
+
The following configuration options control git-trac:
|
75
|
+
trac.url The root URL to the Trac website
|
76
|
+
trac.username Username (if required by site)
|
77
|
+
trac.password Password (if required by site)
|
78
|
+
trac.depth Default --depth for commands which accept it
|
79
|
+
trac.fetch-command Update command to use in lieu of git svn fetch
|
80
|
+
|
81
|
+
They may be set either by editing the git config file or with git config.
|
82
|
+
$ vim .git/config
|
83
|
+
$ git config trac.url http://my.trac.url
|
84
|
+
|
85
|
+
Additionally, the following options can affect git-trac's behavior:
|
86
|
+
core.pager
|
87
|
+
color.diff
|
88
|
+
color.pager
|
89
|
+
|
90
|
+
Tip: `git-trac` can be called as `git trac` with the following alias:
|
91
|
+
$ git config alias.trac '!git-trac'
|
92
|
+
EOF
|
93
|
+
|
94
|
+
TOPICS["rails"] = ["Quick start guide for Rails"] << <<-EOF
|
95
|
+
Example for usage on the Ruby on Rails repository:
|
96
|
+
|
97
|
+
# Export the repository if you haven't already (start it and go to lunch)
|
98
|
+
$ git svn clone --stdlayout http://dev.rubyonrails.org/svn/rails rails
|
99
|
+
$ cd rails
|
100
|
+
|
101
|
+
# This next step is optional but compresses the repository by a factor of 10
|
102
|
+
$ git gc
|
103
|
+
|
104
|
+
# Setup git-trac (see git-trac help config)
|
105
|
+
$ git config trac.url http://dev.rubyonrails.org
|
106
|
+
$ git config trac.username myaccount
|
107
|
+
$ git config trac.password mypassword
|
108
|
+
|
109
|
+
# Have fun
|
110
|
+
$ git-trac checkout 1234567/new_feature_with_failing_tests.patch
|
111
|
+
$ rake
|
112
|
+
$ git-trac checkout --rebase 1234567/new_feature_with_passing_tests.patch
|
113
|
+
# EDIT EDIT EDIT
|
114
|
+
$ git commit -a
|
115
|
+
$ git-trac push 1234567/improved_feature.patch
|
116
|
+
EOF
|
117
|
+
|
118
|
+
TOPICS["arguments"] = ["Definitions for <ticket> and <attachment>"] << <<-EOF
|
119
|
+
A <ticket> is specified by number, which may be optionally prefixed by a slash
|
120
|
+
terminated string.
|
121
|
+
123456
|
122
|
+
trac/123456
|
123
|
+
http://dev.rubyonrails.org/ticket/123456
|
124
|
+
|
125
|
+
An <attachment> is a <ticket> followed by a slash and a filename. Anything
|
126
|
+
in this filename after a slash or a question mark is ignored.
|
127
|
+
123456/foo.patch
|
128
|
+
trac/123456/foo.patch
|
129
|
+
http://dev.rubyonrails.org/attachment/ticket/123456/foo.patch
|
130
|
+
http://dev.rubyonrails.org/attachment/ticket/123456/foo.patch?format=raw
|
131
|
+
|
132
|
+
If a ticket is given to a command expecting a multiple attachments, all the
|
133
|
+
ticket's attachments are used. If the command accepts just one attachment,
|
134
|
+
just the ticket's last attachment is used.
|
135
|
+
EOF
|
136
|
+
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module Git
|
2
|
+
module Trac
|
3
|
+
class Runner
|
4
|
+
|
5
|
+
class Push < Base #:nodoc:
|
6
|
+
|
7
|
+
def self.summary
|
8
|
+
"Upload the current diff against upstream to a ticket"
|
9
|
+
end
|
10
|
+
|
11
|
+
def banner_arguments
|
12
|
+
"[options] <ticket>[/<filename>]"
|
13
|
+
end
|
14
|
+
|
15
|
+
def description
|
16
|
+
<<-EOF
|
17
|
+
Do a `git diff` and upload the result as an attachment to a ticket. The
|
18
|
+
potential patch will be shown in a pager and you will be given the opportunity
|
19
|
+
to cancel. If no filename is given, the name of the current branch plus
|
20
|
+
".patch" is used.
|
21
|
+
|
22
|
+
This command was formerly known as upload-patch.
|
23
|
+
EOF
|
24
|
+
end
|
25
|
+
|
26
|
+
def add_options(opts)
|
27
|
+
opts.on("--against BRANCH", "git diff BRANCH...HEAD") do |b|
|
28
|
+
options[:upstream] = b
|
29
|
+
end
|
30
|
+
opts.on("--description TEXT", "use TEXT as description") do |text|
|
31
|
+
options[:description] = text
|
32
|
+
end
|
33
|
+
opts.on("--[no-]force", "do not prompt before uploading") do |force|
|
34
|
+
options[:force] = force
|
35
|
+
end
|
36
|
+
add_local_option(opts)
|
37
|
+
end
|
38
|
+
|
39
|
+
def run
|
40
|
+
if @argv.size > 1
|
41
|
+
too_many_arguments
|
42
|
+
elsif @argv.empty?
|
43
|
+
number = repository.guess_current_ticket_number or
|
44
|
+
missing_argument "ticket"
|
45
|
+
else
|
46
|
+
number, options[:filename] = parse_attachment(@argv.shift)
|
47
|
+
end
|
48
|
+
fetch_unless_local
|
49
|
+
ticket = repository.ticket(number)
|
50
|
+
options[:upstream] ||= repository.guess_upstream || "refs/remotes/trunk"
|
51
|
+
options[:upstream] += "...HEAD" unless options[:upstream].include?(".")
|
52
|
+
if $stdin.tty? && !options[:force]
|
53
|
+
block = lambda do
|
54
|
+
repository.in_work_tree do
|
55
|
+
system("git","diff", options[:upstream])
|
56
|
+
end
|
57
|
+
description = "##{number} (#{ticket.csv["summary"]}"
|
58
|
+
cols = ENV["COLUMNS"].to_i
|
59
|
+
cols = 80 if cols.zero?
|
60
|
+
description.sub!(/^(.{#{cols-22}}).{4,}/,"\\1...")
|
61
|
+
print "#{description}) Proceed? [yN] "
|
62
|
+
$stdin.gets[0,1] == "y"
|
63
|
+
end
|
64
|
+
else
|
65
|
+
block = lambda { true }
|
66
|
+
end
|
67
|
+
if uri = ticket.upload_patch(options,&block)
|
68
|
+
puts uri
|
69
|
+
else
|
70
|
+
exit 1
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
UploadPatch = Push
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|