ruby: remove GIT_PAGER from environment
[git] / git-request-pull.rb
1 #!git ruby
2
3 require 'date'
4
5 patch = nil
6
7 def usage
8   puts <<EOF
9 usage: git request-pull [options] start url [end]
10
11     -p                    show patch text as well
12
13 EOF
14   exit 1
15 end
16
17 def read_branch_desc(name)
18   git_config() do |key, value|
19     return value if key == "branch.#{name}.description"
20   end
21   return nil
22 end
23
24 def describe(rev)
25   for_each_ref() do |name, sha1, flags|
26     next unless name.start_with?('refs/tags/')
27     next unless peel_ref(name) == get_sha1(rev)
28     return name.skip_prefix('refs/tags/')
29   end
30   return nil
31 end
32
33 def abbr(ref)
34   if (ref =~ %r{^refs/heads/(.*)} || ref =~ %r{^refs/(tags/.*)})
35     return $1
36   end
37   return ref
38 end
39
40 # $head is the token given from the command line, and $tag_name, if
41 # exists, is the tag we are going to show the commit information for.
42 # If that tag exists at the remote and it points at the commit, use it.
43 # Otherwise, if a branch with the same name as $head exists at the remote
44 # and their values match, use that instead.
45 #
46 # Otherwise find a random ref that matches $head_id.
47
48 def get_ref(transport, head_ref, head_id, tag_name)
49   found = nil
50   transport.get_remote_refs().each do |e|
51     sha1 = e.old_sha1
52     next unless sha1 == head_id
53     ref, deref = e.name.scan(/^(\S+?)(\^\{\})?$/).first
54     found = abbr(ref)
55     break if (deref && ref == "refs/tags/#{tag_name}")
56     break if ref == head_ref
57   end
58   return found
59 end
60
61 def parse_buffer(buffer)
62   summary = []
63   date = msg = nil
64   header, body = buffer.split("\n\n", 2)
65   header.each_line do |line|
66     case line
67     when /^committer ([^<>]+) <(\S+)> (.+)$/
68       date = DateTime.strptime($3, '%s %z')
69     end
70   end
71   body.each_line do |l|
72     break if (l.strip.empty?)
73     summary << l.chomp
74   end
75   summary = summary.join(' ')
76   date = date.strftime('%F %T %z')
77   return [summary, date]
78 end
79
80 def show_shortlog(base, head)
81   rev = Git::RevInfo.setup(nil, ['^' + base, head], nil)
82   shortlog(rev.to_a)
83 end
84
85 def show_diff(patch, base, head)
86   rev = Git::RevInfo.setup(nil, ['^' + sha1_to_hex(base), sha1_to_hex(head)], nil)
87   rev.diffopt.stat_width = -1
88   rev.diffopt.stat_graph_width = -1
89   rev.diffopt.output_format = patch ? DIFF_FORMAT_PATCH : DIFF_FORMAT_DIFFSTAT
90   rev.diffopt.output_format |= DIFF_FORMAT_SUMMARY
91   rev.diffopt.detect_rename = DIFF_DETECT_RENAME
92   rev.diffopt.flags |= DIFF_OPT_RECURSIVE
93
94   diff_tree_sha1(base, head, "", rev.diffopt)
95   log_tree_diff_flush(rev)
96 end
97
98 until ARGV.empty?
99   case ARGV.first
100   when '-p'
101     patch = '-p'
102   when '--'
103     ARGV.shift
104     break
105   when /^-/
106     usage
107   else
108     break
109   end
110   ARGV.shift
111 end
112
113 base = ARGV[0]
114 url = ARGV[1]
115 head = ARGV[2] || 'HEAD'
116 branch_name = branch_desc = nil
117
118 usage unless base or url
119
120 _, _, head_ref = dwim_ref(head)
121
122 if head_ref.start_with?('refs/heads')
123   branch_name = head_ref[11..-1]
124   branch_desc = read_branch_desc(branch_name)
125   branch_name = nil if not branch_desc
126 end
127
128 tag_name = describe(head)
129
130 base_id = get_sha1("#{base}^0")
131 die "Not a valid revision: #{base}" unless base_id
132
133 head_id = get_sha1("#{head}^0")
134 die "Not a valid revision: #{head}" unless head_id
135
136 base_commit = Git::Commit.get(base_id)
137 head_commit = Git::Commit.get(head_id)
138
139 merge_bases = get_merge_bases([base_commit, head_commit], 0);
140 die "No commits in common between #{base} and #{head}" unless merge_bases
141
142 merge_base_id = merge_bases.first.sha1
143 merge_base_commit = Git::Commit.get(merge_base_id)
144
145 remote = remote_get(url)
146 transport = transport_get(remote, nil)
147
148 ref = get_ref(transport, head_ref != "HEAD" ? head_ref : nil, head_id, tag_name)
149 url = remote.url.first
150
151 merge_base_summary, merge_base_date = parse_buffer(merge_base_commit.buffer)
152 head_summary, head_date = parse_buffer(head_commit.buffer)
153
154 puts "The following changes since commit %s:
155
156   %s (%s)
157
158 are available in the git repository at:
159
160 " % [merge_base_commit, merge_base_summary, merge_base_date]
161 puts "  #{url}" + (ref ? " #{ref}" : "")
162 puts "
163 for you to fetch changes up to %s:
164
165   %s (%s)
166
167 ----------------------------------------------------------------
168 " % [head_commit, head_summary, head_date]
169
170 if branch_name
171   puts "(from the branch description for #{branch_name} local branch)"
172   puts
173   puts branch_desc
174 end
175
176 if tag_name
177   if ref != "tags/#{tag_name}"
178     $stderr.puts "warn: You locally have #{tag_name} but it does not (yet)"
179     $stderr.puts "warn: appear to be at #{url}"
180     $stderr.puts "warn: Do you want to push it there, perhaps?"
181   end
182   buffer, _ = read_sha1_file(get_sha1(tag_name))
183   puts buffer.scan(/(?:\n\n)(.+)(?:-----BEGIN PGP )?/m).first
184   puts
185 end
186
187 if branch_name || tag_name
188   puts "----------------------------------------------------------------"
189 end
190
191 show_shortlog(base, head)
192 show_diff(patch, merge_base_id, head_id)
193
194 if ! ref
195   $stderr.puts "warn: No branch of #{url} is at:"
196   $stderr.puts "warn:   %s: %s'" % [find_unique_abbrev(head_id, DEFAULT_ABBREV), head_summary]
197   $stderr.puts "warn: Are you sure you pushed '#{abbr(head_ref)}' there?"
198   exit 1
199 end