3 # Copyright 2019 The OpenSSL Project Authors. All Rights Reserved.
5 # Licensed under the Apache License 2.0 (the "License"). You may not use
6 # this file except in compliance with the License. You can obtain a copy
7 # in the file LICENSE in the source distribution or at
8 # https://www.openssl.org/source/license.html
11 # ghlink - convert repository locations into GitHub links
21 my $progname=basename($0);
23 my $repo = "https://github.com/openssl/openssl";
24 my $tree = "$repo/tree";
25 my $blob = "$repo/blob";
27 my $regex_name = "[[:alpha:]._-]+[[:alnum:]._-]*";
28 my $regex_revision = "[[:alnum:]._-]+";
29 my $regex_path = "(?:${regex_name}/)*${regex_name}";
30 my $regex_lineno = "[0-9]+";
32 my $style = 'default';
42 GetOptions('markdown|m' => \$markdown,
43 'permanent|p' => \$permanent,
47 or die "Errors in command line arguments\n";
50 pod2usage(-exitval => 0,
54 pod2usage(-exitval => 0,
63 my $remotes = `git remote -v`;
64 if ($? != 0 or $remotes !~ "openssl\.git") {
65 die "Current directory does not belong to an OpenSSL git repository";
68 my $curr_branch = `git rev-parse --abbrev-ref HEAD`;
71 my $prefix = `git rev-parse --show-prefix`;
75 # some results are cached for efficiency reasons
81 my ($match, $revision, $path, $lineno) = @_;
83 if (!defined($revision)) {
84 $revision = $curr_branch;
87 my $abbrev_commit = ($permanent) ? "" : "--abbrev-commit";
89 if (!defined($commits{$revision})) {
90 my $c = `git rev-list $abbrev_commit -1 $revision -- 2>/dev/null`;
94 $commits{$revision} = "";
95 } elsif ($permanent) {
96 # always use the commit id if --permanent was specified
97 $commits{$revision} = $c;
99 # if a branch name was specified, use if it exists remotely
100 # otherwise, use the commit id
101 `git ls-remote --exit-code $repo --heads refs/heads/$revision`;
102 $commits{$revision} = ($? == 0) ? $revision : $c;
106 $revision = $commits{$revision};
112 my $gitpath = "$revision:$prefix$path";
114 if (!defined($urls{$gitpath})) {
115 # create urls only for objects in the local repository
116 `git rev-parse $gitpath 2>/dev/null`;
118 $urls{$gitpath} = "";
120 $urls{$gitpath} = "$blob/$revision/$prefix$path";
124 my $url = $urls{$gitpath};
130 return defined($lineno) ? "$url#L$lineno" : "$url";
134 my ($match, $revision, $path, $lineno) = @_;
136 my $url = check_url($match, $revision, $path, $lineno);
146 my ($match, $revision, $path, $lineno) = @_;
148 my $url = check_url($match, $revision, $path, $lineno);
154 return "[$match]($url)";
157 my $regex = qr/(?:(${regex_revision}):)?(${regex_path}):(${regex_lineno})?:?/;
160 # list mode: print github links for all matched locations (and discard the rest)
163 my $found = $replace->($&,$1,$2,$3);
170 # replace mode: replace all matched locations in the text with github links
172 s/$regex/$replace->($&,$1,$2,$3)." "/eg;
182 ghlink - convert repository locations into GitHub links
186 ghlink [<option>...] [<file>...]
188 Concatenate the given file(s) to standard output, converting repository
189 locations into GitHub links. If no file is given, read from stdin.
194 The most common usage of ghlink is to add it as an output filter for commands which
195 produce output referring to locations in the repository. A location can be of roughly
198 [<branch-or-commit>:]<path>:[<lineno>[:]]
200 Examples of such commands are git-grep or find:
202 git grep [-n] [-e] <expression> ... | ghlink [<option>...]
204 find -name <pattern> ... | ghlink [<option>...]
206 Alternatively, the output of the commands can be redirected into files which then
207 can be specified as commandline arguments for ghlink.
211 The ghlink filter recognizes the locations and converts them into links to the
212 OpenSSL GitHub repository, depending on the given options. It tries hard to provide
213 only valid locations. For that reason, ghlink requires to be run from within the
214 working copy. It is allowed to run ghlink from subdirectories, because it takes
215 its relative position into account. Branch names in the output (like from git-grep)
216 are recognized, and if they are missing, the current branch is assumed. When creating
217 the link, ghlink attempts to preserve the branch name, provided the branch exists on
218 the remote repository too (e.g. master, OpenSSL_x_y_z-stable). If the branch does not
219 exist remotely (or if the --permanent option was specified), the branch is resolved
220 locally to a commit-id.
228 Print a brief help message and exit.
232 Print the manual page and exit.
234 =item B<--markdown> | B<-m>
236 Convert the locations to links in markdown syntax, with the location in square brackets,
237 followed by the link in parentheses.
239 =item B<--permanent> | B<-p>
241 Always resolve branches to (unabbreviated) commit-ids for printing the link.
243 =item B<--list> | B<-l>
245 List only the links of all locations found, and omit the resto of th text. This option
246 is useful together with the --permanent option to create GitHub permalinks, see EXAMPLES
255 =item B<~/src/openssl$ git grep -n expression | ghlink>
257 Search for an expression locally in the repository and browse the results directly
258 on GitHub by clicking on the URLs using <ctrl>-<left-mouse> (or whatever key binding
259 the terminal supports).
261 =item B<~/src/openssl$ git grep -n expression | ghlink --markdown [--permanent]>
263 Produces output optimized for posting the result on GitHub as an issue or
264 pull request comment. The locations show up as markdown links in the
265 original grep output format.
267 =item B<~/src/openssl$ git grep -n expression | ghlink --permanent --list>
269 Produces output optimized for posting the result on GitHub as an issue or
270 pull request comment. The locations show up as permalink text boxes, which
271 is the reason why --list was output only the permalinks and omit the text.
273 =item B<~/src/openssl$ ghlink --markdown {gdb.log,asan.log,tsan.log,...}>
275 The ghlink tool also works nicely with call stacks produced by gdb, AddressSanitizer,
276 or ThreadSanitizer and facilitates viewing the stack frames directly in the source.
277 It can either be applied directly as output filter or subsequently on the saved