├── .gitignore
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── cmd
├── generate-default-configuration.go
├── root.go
├── serve.go
└── version.go
├── docs
├── smithy.1.scd
└── smithy.yml.5.scd
├── go.mod
├── go.sum
├── main.go
└── pkg
├── go-git-http
├── .gitignore
├── .travis.yml
├── README.md
├── auth
│ ├── auth.go
│ ├── auth_test.go
│ ├── basicauth.go
│ └── basicauth_test.go
├── errors.go
├── events.go
├── git_reader.go
├── githttp.go
├── pktparser.go
├── pktparser_test.go
├── routing.go
├── rpc_reader.go
├── rpc_reader_test.go
├── testdata
│ ├── receive-pack.0
│ ├── receive-pack.1
│ ├── receive-pack.2
│ ├── receive-pack.3
│ ├── upload-pack.0
│ └── upload-pack.1
├── utils.go
└── version.go
└── smithy
├── config.go
├── encoder.go
├── smithy.go
├── static
└── style.css
└── templates
├── 404.html
├── 500.html
├── blob.html
├── commit.html
├── footer.html
├── header.html
├── index.html
├── log.html
├── refs.html
├── repo-index.html
└── tree.html
/.gitignore:
--------------------------------------------------------------------------------
1 | repos
2 | bin
3 | smithy.yml
4 | smithy
5 | smithy.1
6 | smithy.yml.5
7 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # Start from the latest golang base image
2 | FROM golang:latest as builder
3 |
4 | # Add Maintainer Info
5 | LABEL maintainer="Honza Pokorny "
6 |
7 | # Set the Current Working Directory inside the container
8 | WORKDIR /app
9 |
10 | # Copy go mod and sum files
11 | COPY go.mod go.sum ./
12 |
13 | # Copy the source from the current directory to the Working Directory inside the container
14 | COPY . .
15 |
16 | # Build the Go app
17 | RUN make
18 |
19 | ######## Start a new stage from scratch #######
20 | FROM alpine:latest
21 |
22 | RUN apk --no-cache add ca-certificates
23 |
24 | WORKDIR /root/
25 |
26 | # Copy the Pre-built binary file from the previous stage
27 | COPY --from=builder /app/include include
28 | COPY --from=builder /app/config.yaml .
29 | COPY --from=builder /app/smithy .
30 |
31 | # Expose port 8080 to the outside world
32 | EXPOSE 8080
33 |
34 | # Command to run the executable
35 | CMD ["./smithy"]
36 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 |
635 | Copyright (C)
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | Copyright (C)
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
675 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | BUILD_VERSION ?= $(shell git describe --always --abbrev=40 --dirty)
2 |
3 | SCDOC = scdoc
4 | PREFIX?=/usr/local
5 | BINDIR?=$(PREFIX)/bin
6 | SHAREDIR?=$(PREFIX)/share/smithy
7 | MANDIR?=$(PREFIX)/share/man
8 |
9 | LDFLAGS="-X github.com/honza/smithy/cmd.SmithyVersion=${BUILD_VERSION}"
10 | MODCACHE := $(shell go env GOMODCACHE)
11 |
12 | export CGO_ENABLED=0
13 |
14 | all: smithy smithy.yml
15 |
16 | smithy: go.mod pkg/smithy/*
17 | go build -ldflags $(LDFLAGS) -o smithy main.go
18 |
19 | smithy.yml:
20 | ./smithy generate > smithy.yml
21 |
22 | docs:
23 | $(SCDOC) < docs/smithy.1.scd > smithy.1
24 | $(SCDOC) < docs/smithy.yml.5.scd > smithy.yml.5
25 |
26 | install: all
27 | mkdir -m755 -p $(DESTDIR)$(BINDIR) $(DESTDIR)$(SHAREDIR)
28 | cp -f smithy $(DESTDIR)$(BINDIR)/smithy
29 | cp -f smithy.yml $(DESTDIR)$(SHAREDIR)/smithy.yml
30 | cp -f smithy.1 $(DESTDIR)$(MANDIR)/man1/smithy.1 2>/dev/null || true
31 | cp -f smithy.yml.5 $(DESTDIR)$(MANDIR)/man5/smithy.yml.5 2>/dev/null || true
32 |
33 | uninstall: all
34 | rm -r $(DESTDIR)$(BINDIR)/smithy
35 | rm -fr $(DESTDIR)$(SHAREDIR)
36 |
37 | gofmt:
38 | go fmt ./pkg/... ./cmd/...
39 |
40 | clean:
41 | rm -rf smithy smithy.yml smithy.1 smithy.yml.5
42 |
43 | .PHONY:
44 | smithy smithy.yml clean
45 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Smithy
2 | ===============================================================================
3 |
4 | *smithy* (n) A blacksmith's shop; a forge.
5 |
6 | Smithy is a web frontend for git repositories. It's implemented entirely in
7 | Golang, compiles to a single binary, and it's fast and easy to deploy. Smithy
8 | is an alternative to cgit or gitweb, and doesn't seek to compete with Gitea and
9 | the like.
10 |
11 | * Golang
12 | * Single binary
13 | * Easy to deploy
14 | * Fast
15 | * Customizable
16 | * Free software
17 | * Javascript-free
18 |
19 | Building
20 | -------------------------------------------------------------------------------
21 |
22 | The only dependency is [Golang](https://golang.org/) 1.16 or higher.
23 | Contributors to smithy should have the optional
24 | [scdoc](https://sr.ht/~sircmpwn/scdoc) for generating documentation.
25 |
26 | ```
27 | $ git clone https://github.com/honza/smithy
28 | $ make
29 | $ ./smithy --help
30 | ```
31 |
32 | Installing
33 | -------------------------------------------------------------------------------
34 |
35 | We provide a make rule for installing/uninstall smithy. It will also install a
36 | sample configuration file at `/usr/local/share/smithy/smithy.yml` that you can
37 | later use as a guide.
38 |
39 | ```
40 | $ make install
41 | $ make uninstall
42 | ```
43 |
44 | Configuration
45 | -------------------------------------------------------------------------------
46 |
47 | You can generate a sample configuration by issuing `make smithy.yml` command or
48 | directly using the smithy binary:
49 |
50 | ```
51 | $ make smithy.yml # will generate a smithy.yml file
52 | $ smithy generate > config.yml
53 | $ smithy serve --config config.yml
54 | ```
55 |
56 | A sample configuration can be:
57 |
58 | ``` yaml
59 | title: Smithy, a lightweight git forge
60 | description: Publish your git repositories with ease
61 | port: 3456
62 | git:
63 | root: "/var/www/git"
64 | repos:
65 | - path: "some-cool-project"
66 | slug: "some-cool-project"
67 | title: "Some Cool Project"
68 | description: "Something really cool to change the world"
69 | - path: "ugly-hacks"
70 | exclude: true
71 |
72 | static:
73 | root:
74 | prefix: /static/
75 |
76 | templates:
77 | dir:
78 | ```
79 |
80 | Customizing templates and css
81 | -------------------------------------------------------------------------------
82 |
83 | Out of the box, smithy bundles templates and css in the binary. Setting
84 | `static.root`, and `templates.dir` to empty string will cause smithy to use the
85 | bundled assets.
86 |
87 | If you'd like to customize the templates or the css, copy the `include`
88 | directory somewhere, and then set `static.root`, and `templates.dir` to that
89 | directory.
90 |
91 | Demo
92 | -------------------------------------------------------------------------------
93 |
94 | Smithy is currently hosting [itself on my
95 | domain](https://git.pokorny.ca/smithy).
96 |
97 | Contributing
98 | -------------------------------------------------------------------------------
99 |
100 | Contributions are most welcome. You can open a pull request on
101 | [GitHub](https://github.com/honza/smithy), or [email a patch][1] to
102 | `honza@pokorny.ca`.
103 |
104 | [1]: https://git-send-email.io
105 |
106 | License
107 | -------------------------------------------------------------------------------
108 |
109 | This program is free software: you can redistribute it and/or modify it under
110 | the terms of the GNU General Public License as published by the Free Software
111 | Foundation, either version 3 of the License, or (at your option) any later
112 | version.
113 |
114 | This program is distributed in the hope that it will be useful, but WITHOUT ANY
115 | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
116 | PARTICULAR PURPOSE. See the GNU General Public License for more details.
117 |
118 | You should have received a copy of the GNU General Public License along with
119 | this program. If not, see .
120 |
--------------------------------------------------------------------------------
/cmd/generate-default-configuration.go:
--------------------------------------------------------------------------------
1 | // smithy --- the git forge
2 | // Copyright (C) 2020 Honza Pokorny
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU General Public License
15 | // along with this program. If not, see .
16 |
17 | package cmd
18 |
19 | import (
20 | "github.com/honza/smithy/pkg/smithy"
21 | "github.com/spf13/cobra"
22 | )
23 |
24 | var generateDefaultConfigurationCmd = &cobra.Command{
25 | Use: "generate",
26 | Short: "Generate the default smithy configuration",
27 | Run: func(cmd *cobra.Command, args []string) {
28 | smithy.GenerateDefaultConfig()
29 | },
30 | }
31 |
--------------------------------------------------------------------------------
/cmd/root.go:
--------------------------------------------------------------------------------
1 | // smithy --- the git forge
2 | // Copyright (C) 2020 Honza Pokorny
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU General Public License
15 | // along with this program. If not, see .
16 |
17 | package cmd
18 |
19 | import (
20 | "fmt"
21 | "os"
22 |
23 | "github.com/spf13/cobra"
24 | )
25 |
26 | var rootCmd = &cobra.Command{
27 | Use: "smithy",
28 | Short: "Smithy Git Forge",
29 | Long: `A lightweight git forge`,
30 | Run: func(cmd *cobra.Command, args []string) {
31 | cmd.Help()
32 | },
33 | }
34 |
35 | func Execute() {
36 | if err := rootCmd.Execute(); err != nil {
37 | fmt.Println(err)
38 | os.Exit(1)
39 | }
40 | }
41 |
42 | func init() {
43 | rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file path (default is config.yaml)")
44 | rootCmd.PersistentFlags().BoolVar(&debug, "debug", false, "")
45 | rootCmd.AddCommand(generateDefaultConfigurationCmd)
46 | rootCmd.AddCommand(serveCmd)
47 | rootCmd.AddCommand(versionCmd)
48 | }
49 |
--------------------------------------------------------------------------------
/cmd/serve.go:
--------------------------------------------------------------------------------
1 | // smithy --- the git forge
2 | // Copyright (C) 2020 Honza Pokorny
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU General Public License
15 | // along with this program. If not, see .
16 |
17 | package cmd
18 |
19 | import (
20 | "github.com/honza/smithy/pkg/smithy"
21 | "github.com/spf13/cobra"
22 | )
23 |
24 | var cfgFile string
25 | var debug bool
26 |
27 | var serveCmd = &cobra.Command{
28 | Use: "serve",
29 | Short: "Start the smithy server",
30 | Run: func(cmd *cobra.Command, args []string) {
31 | smithy.StartServer(cfgFile, debug)
32 | },
33 | }
34 |
--------------------------------------------------------------------------------
/cmd/version.go:
--------------------------------------------------------------------------------
1 | // smithy --- the git forge
2 | // Copyright (C) 2020 Honza Pokorny
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU General Public License
15 | // along with this program. If not, see .
16 |
17 | package cmd
18 |
19 | import (
20 | "fmt"
21 | "github.com/spf13/cobra"
22 | )
23 |
24 | var SmithyVersion string
25 |
26 | var versionCmd = &cobra.Command{
27 | Use: "version",
28 | Short: "Show smithy version",
29 | Run: func(cmd *cobra.Command, args []string) {
30 | if SmithyVersion == "" {
31 | fmt.Println("dev")
32 | return
33 | }
34 | fmt.Println(SmithyVersion)
35 | },
36 | }
37 |
--------------------------------------------------------------------------------
/docs/smithy.1.scd:
--------------------------------------------------------------------------------
1 | smithy(1)
2 |
3 | # NAME
4 |
5 | smithy - a small git forge
6 |
7 | # SYNOPSIS
8 |
9 | *smithy* _command_ [<_options_>...]
10 |
11 | # DESCRIPTION
12 |
13 | *smithy* is a web frontend for git repositories. It's implemented entirely in
14 | Golang, compiles to a single binary, and it's fast and easy to deploy. Smithy
15 | is an alternative to cgit or gitweb, and doesn't seek to compete with Gitea and
16 | the like.
17 |
18 | # COMMANDS
19 |
20 | *generate*
21 | Generate a sample configuration file, outputs to *STDOUT*.
22 | Check *smithy.yml(5)* for more information.
23 |
24 | *serve --config path/to/config.toml*
25 | Serve the application, you'll need to supply a configuration file.
26 | Outputs its log to *STDOUT*.
27 |
28 | # GLOBAL FLAGS
29 |
30 | *--debug*
31 | Display debug messages to *STDOUT*.
32 |
33 | *--config *
34 | Use the given configuration file. See *smithy.yml(5)* for a reference.
35 |
36 | # AUTHORS
37 |
38 | Maintained by Honza Pokorny , who is assisted by other free
39 | software contributors. For more information about smithy development, see
40 | https://github.com/honza/smithy.
41 |
--------------------------------------------------------------------------------
/docs/smithy.yml.5.scd:
--------------------------------------------------------------------------------
1 | smithy.yml(5)
2 |
3 | # NAME
4 |
5 | *smithy.yml* - configuration file for *smithy*(1)
6 |
7 | # DESCRIPTION
8 |
9 | This file describes where smithy should scan for repositories, their
10 | respectives titles, description and slug. Also if it should include the default
11 | styles and assets or if it should load from a respective directory.
12 |
13 | # GLOBAL DIRECTIVES
14 |
15 | *host: *
16 | Address will be displayed on a repository, indicating the URL he can use
17 | to clone.
18 |
19 | *port: <...>*
20 | Port to serve smithy from. You can use a reverse-proxy (nginx, apache) to
21 | expose smithy.
22 |
23 | # GIT DIRECTIVES
24 |
25 | *root: *
26 | The main directory where smithy should scan for repositories.
27 |
28 | *repos*
29 | A list of repositories and their respective configurations.
30 |
31 | # STATIC DIRECTIVES
32 |
33 | If you'd like to customize the templates or the css, you can grab the source
34 | code, copy the `include` directory somewhere, and then set `root`, and
35 | `templates.dir` to that directory.
36 |
37 | *root: *
38 | When set to an empty string, it will load the static assets bundled within
39 | the project.
40 |
41 | *prefix: *
42 | A given prefix that all assets will receive.
43 |
44 | # TEMPLATES DIRECTIVES
45 |
46 | *dir: *
47 | The directory to load templates from.
48 |
49 | # EXAMPLE CONFIGURATION
50 |
51 | When manually building smithy from source, a sample config file will be
52 | included on `/usr/local/share/smithy/smithy.yml`.
53 |
54 | ```
55 | title: Smithy, a lightweight git force
56 | description: Publish your git repositories with ease
57 | host: git.example.com
58 | port: 3456
59 | git:
60 | root: "/srv/git"
61 | repos:
62 | - path: "git"
63 | slug: "git"
64 | title: "git"
65 | description: "git is a fast, scalable distributed revision control system"
66 | static:
67 | root: ""
68 | prefix: /static/
69 | templates:
70 | dir: ""
71 | ```
72 |
73 | # AUTHORS
74 |
75 | Maintained by Honza Pokorny , who is assisted by other free
76 | software contributors. For more information about smithy development, see
77 | https://github.com/honza/smithy.
78 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/honza/smithy
2 |
3 | go 1.17
4 |
5 | require (
6 | github.com/alecthomas/chroma v0.8.2
7 | github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 // indirect
8 | github.com/dlclark/regexp2 v1.2.0 // indirect
9 | github.com/emirpasic/gods v1.12.0 // indirect
10 | github.com/gin-contrib/sse v0.1.0 // indirect
11 | github.com/gin-gonic/gin v1.6.3
12 | github.com/go-git/gcfg v1.5.0 // indirect
13 | github.com/go-git/go-billy/v5 v5.0.0 // indirect
14 | github.com/go-git/go-git/v5 v5.1.0
15 | github.com/go-playground/locales v0.13.0 // indirect
16 | github.com/go-playground/universal-translator v0.17.0 // indirect
17 | github.com/go-playground/validator/v10 v10.2.0 // indirect
18 | github.com/golang/protobuf v1.3.3 // indirect
19 | github.com/imdario/mergo v0.3.9 // indirect
20 | github.com/inconshreveable/mousetrap v1.0.0 // indirect
21 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
22 | github.com/json-iterator/go v1.1.9 // indirect
23 | github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd // indirect
24 | github.com/leodido/go-urn v1.2.0 // indirect
25 | github.com/mattn/go-isatty v0.0.12 // indirect
26 | github.com/mitchellh/go-homedir v1.1.0 // indirect
27 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
28 | github.com/modern-go/reflect2 v1.0.1 // indirect
29 | github.com/sergi/go-diff v1.1.0 // indirect
30 | github.com/spf13/cobra v1.0.0
31 | github.com/spf13/pflag v1.0.3 // indirect
32 | github.com/ugorji/go/codec v1.1.7 // indirect
33 | github.com/xanzy/ssh-agent v0.2.1 // indirect
34 | github.com/yuin/goldmark v1.2.1
35 | github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691
36 | golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 // indirect
37 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a // indirect
38 | golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 // indirect
39 | gopkg.in/warnings.v0 v0.1.2 // indirect
40 | gopkg.in/yaml.v2 v2.2.8
41 | )
42 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
3 | github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
4 | github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0=
5 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
6 | github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
7 | github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
8 | github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
9 | github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U=
10 | github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
11 | github.com/alecthomas/chroma v0.7.2-0.20200305040604-4f3623dce67a/go.mod h1:fv5SzZPFJbwp2NXJWpFIX7DZS4HgV1K4ew4Pc2OZD9s=
12 | github.com/alecthomas/chroma v0.8.2 h1:x3zkuE2lUk/RIekyAJ3XRqSCP4zwWDfcw/YJCuCAACg=
13 | github.com/alecthomas/chroma v0.8.2/go.mod h1:sko8vR34/90zvl5QdcUdvzL3J8NKjAUx9va9jPuFNoM=
14 | github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo=
15 | github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=
16 | github.com/alecthomas/kong v0.1.17-0.20190424132513-439c674f7ae0/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI=
17 | github.com/alecthomas/kong v0.2.1-0.20190708041108-0548c6b1afae/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI=
18 | github.com/alecthomas/kong v0.2.4/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE=
19 | github.com/alecthomas/kong-hcl v0.1.8-0.20190615233001-b21fea9723c8/go.mod h1:MRgZdU3vrFd05IQ89AxUZ0aYdF39BYoNFa324SodPCA=
20 | github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 h1:p9Sln00KOTlrYkxI1zYWl1QLnEqAqEARBEYa8FQnQcY=
21 | github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
22 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
23 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
24 | github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
25 | github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
26 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
27 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
28 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
29 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
30 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
31 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
32 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
33 | github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
34 | github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
35 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
36 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
37 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
38 | github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
39 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
40 | github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E=
41 | github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ=
42 | github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk=
43 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
44 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
45 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
46 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
47 | github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
48 | github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
49 | github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk=
50 | github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
51 | github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
52 | github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
53 | github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
54 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
55 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
56 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
57 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
58 | github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
59 | github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
60 | github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
61 | github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
62 | github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
63 | github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
64 | github.com/go-git/go-billy/v5 v5.0.0 h1:7NQHvd9FVid8VL4qVUMm8XifBK+2xCoZ2lSk0agRrHM=
65 | github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
66 | github.com/go-git/go-git-fixtures/v4 v4.0.1 h1:q+IFMfLx200Q3scvt2hN79JsEzy4AmBTp/pqnefH+Bc=
67 | github.com/go-git/go-git-fixtures/v4 v4.0.1/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw=
68 | github.com/go-git/go-git/v5 v5.1.0 h1:HxJn9g/E7eYvKW3Fm7Jt4ee8LXfPOm/H1cdDu8vEssk=
69 | github.com/go-git/go-git/v5 v5.1.0/go.mod h1:ZKfuPUoY1ZqIG4QG9BDBh3G4gLM5zvPuSJAozQrZuyM=
70 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
71 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
72 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
73 | github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
74 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
75 | github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
76 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
77 | github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
78 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
79 | github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
80 | github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
81 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
82 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
83 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
84 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
85 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
86 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
87 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
88 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
89 | github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
90 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
91 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
92 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
93 | github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
94 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
95 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
96 | github.com/gorilla/csrf v1.6.0/go.mod h1:7tSf8kmjNYr7IWDCYhd3U8Ck34iQ/Yw5CJu7bAkHEGI=
97 | github.com/gorilla/handlers v1.4.1/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
98 | github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
99 | github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
100 | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
101 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
102 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
103 | github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
104 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
105 | github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg=
106 | github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
107 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
108 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
109 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
110 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
111 | github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
112 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
113 | github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
114 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
115 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
116 | github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY=
117 | github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
118 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
119 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
120 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
121 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
122 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
123 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
124 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
125 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
126 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
127 | github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
128 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
129 | github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
130 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
131 | github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
132 | github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
133 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
134 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
135 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
136 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
137 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
138 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
139 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
140 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
141 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
142 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
143 | github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
144 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
145 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
146 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
147 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
148 | github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E=
149 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
150 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
151 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
152 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
153 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
154 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
155 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
156 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
157 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
158 | github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
159 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
160 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
161 | github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
162 | github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
163 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
164 | github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
165 | github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
166 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
167 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
168 | github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
169 | github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
170 | github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
171 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
172 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
173 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
174 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
175 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
176 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
177 | github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8=
178 | github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
179 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
180 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
181 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
182 | github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
183 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
184 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
185 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
186 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
187 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
188 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
189 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
190 | github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
191 | github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
192 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
193 | github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
194 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
195 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
196 | github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
197 | github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70=
198 | github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
199 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
200 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
201 | github.com/yuin/goldmark v1.1.22/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
202 | github.com/yuin/goldmark v1.2.1 h1:ruQGxdhGHe7FWOJPT0mKs5+pD2Xs1Bm/kdGlHO04FmM=
203 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
204 | github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691 h1:VWSxtAiQNh3zgHJpdpkpVYjTPqRE3P6UZCOPa1nRDio=
205 | github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691/go.mod h1:YLF3kDffRfUH/bTxOxHhV6lxwIB3Vfj91rEwNMS9MXo=
206 | go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
207 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
208 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
209 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
210 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
211 | golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
212 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
213 | golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM=
214 | golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
215 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
216 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
217 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
218 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
219 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
220 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
221 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
222 | golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
223 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
224 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
225 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
226 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
227 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
228 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
229 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
230 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
231 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
232 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
233 | golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
234 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
235 | golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
236 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
237 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
238 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
239 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
240 | golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 h1:opSr2sbRXk5X5/givKrrKj9HXxFpW2sdCiP8MJSKLQY=
241 | golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
242 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
243 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
244 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
245 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
246 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
247 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
248 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
249 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
250 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
251 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
252 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
253 | google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
254 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
255 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
256 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
257 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
258 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
259 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
260 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
261 | gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
262 | gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
263 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
264 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
265 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
266 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
267 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
268 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
269 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
270 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | // smithy --- the git forge
2 | // Copyright (C) 2020 Honza Pokorny
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU General Public License
15 | // along with this program. If not, see .
16 |
17 | package main
18 |
19 | import (
20 | "github.com/honza/smithy/cmd"
21 | )
22 |
23 | func main() {
24 | cmd.Execute()
25 | }
26 |
--------------------------------------------------------------------------------
/pkg/go-git-http/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled Object files, Static and Dynamic libs (Shared Objects)
2 | *.o
3 | *.a
4 | *.so
5 |
6 | # Folders
7 | _obj
8 | _test
9 |
10 | # Architecture specific extensions/prefixes
11 | *.[568vq]
12 | [568vq].out
13 |
14 | *.cgo1.go
15 | *.cgo2.c
16 | _cgo_defun.c
17 | _cgo_gotypes.go
18 | _cgo_export.*
19 |
20 | _testmain.go
21 |
22 | *.exe
23 | *.test
24 |
--------------------------------------------------------------------------------
/pkg/go-git-http/.travis.yml:
--------------------------------------------------------------------------------
1 | language: go
2 |
3 | go:
4 | - 1.5
5 | - tip
6 |
--------------------------------------------------------------------------------
/pkg/go-git-http/README.md:
--------------------------------------------------------------------------------
1 | Source: https://github.com/AaronO/go-git-http
2 | Original code released under Apache License 2.0
3 |
4 | Changes: add "git/" prefix to all routes
5 |
--------------------------------------------------------------------------------
/pkg/go-git-http/auth/auth.go:
--------------------------------------------------------------------------------
1 | package auth
2 |
3 | import (
4 | "net/http"
5 | "regexp"
6 | "strings"
7 | )
8 |
9 | type AuthInfo struct {
10 | // Usernane or email
11 | Username string
12 | // Plaintext password or token
13 | Password string
14 |
15 | // repo component of URL
16 | // Usually: "username/repo_name"
17 | // But could also be: "some_repo.git"
18 | Repo string
19 |
20 | // Are we pushing or fetching ?
21 | Push bool
22 | Fetch bool
23 | }
24 |
25 | var (
26 | repoNameRegex = regexp.MustCompile("^/?(.*?)/(HEAD|git-upload-pack|git-receive-pack|info/refs|objects/.*)$")
27 | )
28 |
29 | func Authenticator(authf func(AuthInfo) (bool, error)) func(http.Handler) http.Handler {
30 | return func(handler http.Handler) http.Handler {
31 | return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
32 | auth, err := parseAuthHeader(req.Header.Get("Authorization"))
33 | if err != nil {
34 | w.Header().Set("WWW-Authenticate", `Basic realm="git server"`)
35 | http.Error(w, err.Error(), 401)
36 | return
37 | }
38 |
39 | // Build up info from request headers and URL
40 | info := AuthInfo{
41 | Username: auth.Name,
42 | Password: auth.Pass,
43 | Repo: repoName(req.URL.Path),
44 | Push: isPush(req),
45 | Fetch: isFetch(req),
46 | }
47 |
48 | // Call authentication function
49 | authenticated, err := authf(info)
50 | if err != nil {
51 | code := 500
52 | msg := err.Error()
53 | if se, ok := err.(StatusError); ok {
54 | code = se.StatusCode()
55 | }
56 | http.Error(w, msg, code)
57 | return
58 | }
59 |
60 | // Deny access to repo
61 | if !authenticated {
62 | http.Error(w, "Forbidden", 403)
63 | return
64 | }
65 |
66 | // Access granted
67 | handler.ServeHTTP(w, req)
68 | })
69 | }
70 | }
71 |
72 | func isFetch(req *http.Request) bool {
73 | return isService("upload-pack", req)
74 | }
75 |
76 | func isPush(req *http.Request) bool {
77 | return isService("receive-pack", req)
78 | }
79 |
80 | func isService(service string, req *http.Request) bool {
81 | return getServiceType(req) == service || strings.HasSuffix(req.URL.Path, service)
82 | }
83 |
84 | func repoName(urlPath string) string {
85 | matches := repoNameRegex.FindStringSubmatch(urlPath)
86 | if matches == nil {
87 | return ""
88 | }
89 | return matches[1]
90 | }
91 |
92 | func getServiceType(r *http.Request) string {
93 | service_type := r.FormValue("service")
94 |
95 | if s := strings.HasPrefix(service_type, "git-"); !s {
96 | return ""
97 | }
98 |
99 | return strings.Replace(service_type, "git-", "", 1)
100 | }
101 |
102 | // StatusCode is an interface allowing authenticators
103 | // to pass down error's with an http error code
104 | type StatusError interface {
105 | StatusCode() int
106 | }
107 |
--------------------------------------------------------------------------------
/pkg/go-git-http/auth/auth_test.go:
--------------------------------------------------------------------------------
1 | package auth
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func TestRepoName(t *testing.T) {
8 | if x := repoName("/yapp.ss.git/HEAD"); x != "yapp.ss.git" {
9 | t.Errorf("Should have been 'yapp.js.git' is '%s'", x)
10 | }
11 |
12 | if x := repoName("aarono/gogo-proxy/HEAD"); x != "aarono/gogo-proxy" {
13 | t.Errorf("Should have been 'aarono/gogo-proxy' is '%s'", x)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/pkg/go-git-http/auth/basicauth.go:
--------------------------------------------------------------------------------
1 | package auth
2 |
3 | import (
4 | "encoding/base64"
5 | "fmt"
6 | "regexp"
7 | "strings"
8 | )
9 |
10 | // Parse http basic header
11 | type BasicAuth struct {
12 | Name string
13 | Pass string
14 | }
15 |
16 | var (
17 | basicAuthRegex = regexp.MustCompile("^([^:]*):(.*)$")
18 | )
19 |
20 | func parseAuthHeader(header string) (*BasicAuth, error) {
21 | parts := strings.SplitN(header, " ", 2)
22 | if len(parts) < 2 {
23 | return nil, fmt.Errorf("Invalid authorization header, not enought parts")
24 | }
25 |
26 | authType := parts[0]
27 | authData := parts[1]
28 |
29 | if strings.ToLower(authType) != "basic" {
30 | return nil, fmt.Errorf("Authentication '%s' was not of 'Basic' type", authType)
31 | }
32 |
33 | data, err := base64.StdEncoding.DecodeString(authData)
34 | if err != nil {
35 | return nil, err
36 | }
37 |
38 | matches := basicAuthRegex.FindStringSubmatch(string(data))
39 | if matches == nil {
40 | return nil, fmt.Errorf("Authorization data '%s' did not match auth regexp", data)
41 | }
42 |
43 | return &BasicAuth{
44 | Name: matches[1],
45 | Pass: matches[2],
46 | }, nil
47 | }
48 |
--------------------------------------------------------------------------------
/pkg/go-git-http/auth/basicauth_test.go:
--------------------------------------------------------------------------------
1 | package auth
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func TestHeaderParsing(t *testing.T) {
8 | // Basic admin:password
9 | authorization := "Basic YWRtaW46cGFzc3dvcmQ="
10 |
11 | auth, err := parseAuthHeader(authorization)
12 | if err != nil {
13 | t.Error(err)
14 | }
15 |
16 | if auth.Name != "admin" {
17 | t.Errorf("Detected name does not match: '%s'", auth.Name)
18 | }
19 | if auth.Pass != "password" {
20 | t.Errorf("Detected password does not match: '%s'", auth.Pass)
21 | }
22 | }
23 |
24 | func TestEmptyHeader(t *testing.T) {
25 | if _, err := parseAuthHeader(""); err == nil {
26 | t.Errorf("Empty headers should generate errors")
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/pkg/go-git-http/errors.go:
--------------------------------------------------------------------------------
1 | package githttp
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | type ErrorNoAccess struct {
8 | // Path to directory of repo accessed
9 | Dir string
10 | }
11 |
12 | func (e *ErrorNoAccess) Error() string {
13 | return fmt.Sprintf("Could not access repo at '%s'", e.Dir)
14 | }
15 |
--------------------------------------------------------------------------------
/pkg/go-git-http/events.go:
--------------------------------------------------------------------------------
1 | package githttp
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 | )
7 |
8 | // An event (triggered on push/pull)
9 | type Event struct {
10 | // One of tag/push/fetch
11 | Type EventType `json:"type"`
12 |
13 | ////
14 | // Set for pushes and pulls
15 | ////
16 |
17 | // SHA of commit
18 | Commit string `json:"commit"`
19 |
20 | // Path to bare repo
21 | Dir string
22 |
23 | ////
24 | // Set for pushes or tagging
25 | ////
26 | Tag string `json:"tag,omitempty"`
27 | Last string `json:"last,omitempty"`
28 | Branch string `json:"branch,omitempty"`
29 |
30 | // Error contains the error that happened (if any)
31 | // during this action/event
32 | Error error
33 |
34 | // Http stuff
35 | Request *http.Request
36 | }
37 |
38 | type EventType int
39 |
40 | // Possible event types
41 | const (
42 | TAG = iota + 1
43 | PUSH
44 | FETCH
45 | PUSH_FORCE
46 | )
47 |
48 | func (e EventType) String() string {
49 | switch e {
50 | case TAG:
51 | return "tag"
52 | case PUSH:
53 | return "push"
54 | case PUSH_FORCE:
55 | return "push-force"
56 | case FETCH:
57 | return "fetch"
58 | }
59 | return "unknown"
60 | }
61 |
62 | func (e EventType) MarshalJSON() ([]byte, error) {
63 | return []byte(fmt.Sprintf(`"%s"`, e)), nil
64 | }
65 |
66 | func (e EventType) UnmarshalJSON(data []byte) error {
67 | str := string(data[:])
68 | switch str {
69 | case "tag":
70 | e = TAG
71 | case "push":
72 | e = PUSH
73 | case "push-force":
74 | e = PUSH_FORCE
75 | case "fetch":
76 | e = FETCH
77 | default:
78 | return fmt.Errorf("'%s' is not a known git event type")
79 | }
80 | return nil
81 | }
82 |
--------------------------------------------------------------------------------
/pkg/go-git-http/git_reader.go:
--------------------------------------------------------------------------------
1 | package githttp
2 |
3 | import (
4 | "errors"
5 | "io"
6 | "regexp"
7 | )
8 |
9 | // GitReader scans for errors in the output of a git command
10 | type GitReader struct {
11 | // Underlying reader (to relay calls to)
12 | io.Reader
13 |
14 | // Error
15 | GitError error
16 | }
17 |
18 | // Regex to detect errors
19 | var (
20 | gitErrorRegex = regexp.MustCompile("error: (.*)")
21 | )
22 |
23 | // Implement the io.Reader interface
24 | func (g *GitReader) Read(p []byte) (n int, err error) {
25 | // Relay call
26 | n, err = g.Reader.Read(p)
27 |
28 | // Scan for errors
29 | g.scan(p)
30 |
31 | return n, err
32 | }
33 |
34 | func (g *GitReader) scan(data []byte) {
35 | // Already got an error
36 | // the main error will be the first error line
37 | if g.GitError != nil {
38 | return
39 | }
40 |
41 | matches := gitErrorRegex.FindSubmatch(data)
42 |
43 | // Skip, no matches found
44 | if matches == nil {
45 | return
46 | }
47 |
48 | g.GitError = errors.New(string(matches[1][:]))
49 | }
50 |
--------------------------------------------------------------------------------
/pkg/go-git-http/githttp.go:
--------------------------------------------------------------------------------
1 | package githttp
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "net/http"
7 | "os"
8 | "os/exec"
9 | "path"
10 | "strings"
11 | )
12 |
13 | type GitHttp struct {
14 | // Root directory to serve repos from
15 | ProjectRoot string
16 |
17 | // Path to git binary
18 | GitBinPath string
19 |
20 | // Access rules
21 | UploadPack bool
22 | ReceivePack bool
23 |
24 | // Event handling functions
25 | EventHandler func(ev Event)
26 | }
27 |
28 | // Implement the http.Handler interface
29 | func (g *GitHttp) ServeHTTP(w http.ResponseWriter, r *http.Request) {
30 | g.requestHandler(w, r)
31 | return
32 | }
33 |
34 | // Shorthand constructor for most common scenario
35 | func New(root string) *GitHttp {
36 | return &GitHttp{
37 | ProjectRoot: root,
38 | GitBinPath: "/usr/bin/git",
39 | UploadPack: true,
40 | ReceivePack: true,
41 | }
42 | }
43 |
44 | // Build root directory if doesn't exist
45 | func (g *GitHttp) Init() (*GitHttp, error) {
46 | if err := os.MkdirAll(g.ProjectRoot, os.ModePerm); err != nil {
47 | return nil, err
48 | }
49 | return g, nil
50 | }
51 |
52 | // Publish event if EventHandler is set
53 | func (g *GitHttp) event(e Event) {
54 | if g.EventHandler != nil {
55 | g.EventHandler(e)
56 | } else {
57 | fmt.Printf("EVENT: %q\n", e)
58 | }
59 | }
60 |
61 | // Actual command handling functions
62 |
63 | func (g *GitHttp) serviceRpc(hr HandlerReq) error {
64 | w, r, rpc, dir := hr.w, hr.r, hr.Rpc, hr.Dir
65 |
66 | access, err := g.hasAccess(r, dir, rpc, true)
67 | if err != nil {
68 | return err
69 | }
70 |
71 | if access == false {
72 | return &ErrorNoAccess{hr.Dir}
73 | }
74 |
75 | // Reader that decompresses if necessary
76 | reader, err := requestReader(r)
77 | if err != nil {
78 | return err
79 | }
80 | defer reader.Close()
81 |
82 | // Reader that scans for events
83 | rpcReader := &RpcReader{
84 | Reader: reader,
85 | Rpc: rpc,
86 | }
87 |
88 | // Set content type
89 | w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-result", rpc))
90 |
91 | args := []string{rpc, "--stateless-rpc", "."}
92 | cmd := exec.Command(g.GitBinPath, args...)
93 | cmd.Dir = dir
94 | stdin, err := cmd.StdinPipe()
95 | if err != nil {
96 | return err
97 | }
98 |
99 | stdout, err := cmd.StdoutPipe()
100 | if err != nil {
101 | return err
102 | }
103 | defer stdout.Close()
104 |
105 | err = cmd.Start()
106 | if err != nil {
107 | return err
108 | }
109 |
110 | // Scan's git command's output for errors
111 | gitReader := &GitReader{
112 | Reader: stdout,
113 | }
114 |
115 | // Copy input to git binary
116 | io.Copy(stdin, rpcReader)
117 | stdin.Close()
118 |
119 | // Write git binary's output to http response
120 | io.Copy(w, gitReader)
121 |
122 | // Wait till command has completed
123 | mainError := cmd.Wait()
124 |
125 | if mainError == nil {
126 | mainError = gitReader.GitError
127 | }
128 |
129 | // Fire events
130 | for _, e := range rpcReader.Events {
131 | // Set directory to current repo
132 | e.Dir = dir
133 | e.Request = hr.r
134 | e.Error = mainError
135 |
136 | // Fire event
137 | g.event(e)
138 | }
139 |
140 | // Because a response was already written,
141 | // the header cannot be changed
142 | return nil
143 | }
144 |
145 | func (g *GitHttp) getInfoRefs(hr HandlerReq) error {
146 | w, r, dir := hr.w, hr.r, hr.Dir
147 | service_name := getServiceType(r)
148 | access, err := g.hasAccess(r, dir, service_name, false)
149 | if err != nil {
150 | return err
151 | }
152 |
153 | if !access {
154 | g.updateServerInfo(dir)
155 | hdrNocache(w)
156 | return sendFile("text/plain; charset=utf-8", hr)
157 | }
158 |
159 | args := []string{service_name, "--stateless-rpc", "--advertise-refs", "."}
160 | refs, err := g.gitCommand(dir, args...)
161 | if err != nil {
162 | return err
163 | }
164 |
165 | hdrNocache(w)
166 | w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-advertisement", service_name))
167 | w.WriteHeader(http.StatusOK)
168 | w.Write(packetWrite("# service=git-" + service_name + "\n"))
169 | w.Write(packetFlush())
170 | w.Write(refs)
171 |
172 | return nil
173 | }
174 |
175 | func (g *GitHttp) getInfoPacks(hr HandlerReq) error {
176 | hdrCacheForever(hr.w)
177 | return sendFile("text/plain; charset=utf-8", hr)
178 | }
179 |
180 | func (g *GitHttp) getLooseObject(hr HandlerReq) error {
181 | hdrCacheForever(hr.w)
182 | return sendFile("application/x-git-loose-object", hr)
183 | }
184 |
185 | func (g *GitHttp) getPackFile(hr HandlerReq) error {
186 | hdrCacheForever(hr.w)
187 | return sendFile("application/x-git-packed-objects", hr)
188 | }
189 |
190 | func (g *GitHttp) getIdxFile(hr HandlerReq) error {
191 | hdrCacheForever(hr.w)
192 | return sendFile("application/x-git-packed-objects-toc", hr)
193 | }
194 |
195 | func (g *GitHttp) getTextFile(hr HandlerReq) error {
196 | hdrNocache(hr.w)
197 | return sendFile("text/plain", hr)
198 | }
199 |
200 | // Logic helping functions
201 |
202 | func sendFile(content_type string, hr HandlerReq) error {
203 | w, r := hr.w, hr.r
204 | req_file := path.Join(hr.Dir, hr.File)
205 |
206 | f, err := os.Stat(req_file)
207 | if err != nil {
208 | return err
209 | }
210 |
211 | w.Header().Set("Content-Type", content_type)
212 | w.Header().Set("Content-Length", fmt.Sprintf("%d", f.Size()))
213 | w.Header().Set("Last-Modified", f.ModTime().Format(http.TimeFormat))
214 | http.ServeFile(w, r, req_file)
215 |
216 | return nil
217 | }
218 |
219 | func (g *GitHttp) getGitDir(file_path string) (string, error) {
220 | root := g.ProjectRoot
221 |
222 | if root == "" {
223 | cwd, err := os.Getwd()
224 |
225 | if err != nil {
226 | return "", err
227 | }
228 |
229 | root = cwd
230 | }
231 |
232 | f := path.Join(root, file_path)
233 | if _, err := os.Stat(f); os.IsNotExist(err) {
234 | return "", err
235 | }
236 |
237 | return f, nil
238 | }
239 |
240 | func (g *GitHttp) hasAccess(r *http.Request, dir string, rpc string, check_content_type bool) (bool, error) {
241 | if check_content_type {
242 | if r.Header.Get("Content-Type") != fmt.Sprintf("application/x-git-%s-request", rpc) {
243 | return false, nil
244 | }
245 | }
246 |
247 | if !(rpc == "upload-pack" || rpc == "receive-pack") {
248 | return false, nil
249 | }
250 | if rpc == "receive-pack" {
251 | return g.ReceivePack, nil
252 | }
253 | if rpc == "upload-pack" {
254 | return g.UploadPack, nil
255 | }
256 |
257 | return g.getConfigSetting(rpc, dir)
258 | }
259 |
260 | func (g *GitHttp) getConfigSetting(service_name string, dir string) (bool, error) {
261 | service_name = strings.Replace(service_name, "-", "", -1)
262 | setting, err := g.getGitConfig("http."+service_name, dir)
263 | if err != nil {
264 | return false, nil
265 | }
266 |
267 | if service_name == "uploadpack" {
268 | return setting != "false", nil
269 | }
270 |
271 | return setting == "true", nil
272 | }
273 |
274 | func (g *GitHttp) getGitConfig(config_name string, dir string) (string, error) {
275 | args := []string{"config", config_name}
276 | out, err := g.gitCommand(dir, args...)
277 | if err != nil {
278 | return "", err
279 | }
280 | return string(out)[0 : len(out)-1], nil
281 | }
282 |
283 | func (g *GitHttp) updateServerInfo(dir string) ([]byte, error) {
284 | args := []string{"update-server-info"}
285 | return g.gitCommand(dir, args...)
286 | }
287 |
288 | func (g *GitHttp) gitCommand(dir string, args ...string) ([]byte, error) {
289 | command := exec.Command(g.GitBinPath, args...)
290 | command.Dir = dir
291 |
292 | return command.Output()
293 | }
294 |
--------------------------------------------------------------------------------
/pkg/go-git-http/pktparser.go:
--------------------------------------------------------------------------------
1 | package githttp
2 |
3 | import (
4 | "encoding/hex"
5 | "errors"
6 | "fmt"
7 | )
8 |
9 | // pktLineParser is a parser for git pkt-line Format,
10 | // as documented in https://github.com/git/git/blob/master/Documentation/technical/protocol-common.txt.
11 | // A zero value of pktLineParser is valid to use as a parser in ready state.
12 | // Output should be read from Lines and Error after Step returns finished true.
13 | // pktLineParser reads until a terminating "0000" flush-pkt. It's good for a single use only.
14 | type pktLineParser struct {
15 | // Lines contains all pkt-lines.
16 | Lines []string
17 |
18 | // Error contains the first error encountered while parsing, or nil otherwise.
19 | Error error
20 |
21 | // Internal state machine.
22 | state state
23 | next int // next is the number of bytes that need to be written to buf before its contents should be processed by the state machine.
24 | buf []byte
25 | }
26 |
27 | // Feed accumulates and parses data.
28 | // It will return early if it reaches end of pkt-line data (indicated by a flush-pkt "0000"),
29 | // or if it encounters a parsing error.
30 | // It must not be called when state is done.
31 | // When done, all of pkt-lines will be available in Lines, and Error will be set if any error occurred.
32 | func (p *pktLineParser) Feed(data []byte) {
33 | for {
34 | // If not enough data to reach next state, append it to buf and return.
35 | if len(data) < p.next {
36 | p.buf = append(p.buf, data...)
37 | p.next -= len(data)
38 | return
39 | }
40 |
41 | // There's enough data to reach next state. Take from data only what's needed.
42 | b := data[:p.next]
43 | data = data[p.next:]
44 | p.buf = append(p.buf, b...)
45 | p.next = 0
46 |
47 | // Take a step to next state.
48 | err := p.step()
49 | if err != nil {
50 | p.state = done
51 | p.Error = err
52 | return
53 | }
54 |
55 | // Break out once reached done state.
56 | if p.state == done {
57 | return
58 | }
59 | }
60 | }
61 |
62 | const (
63 | // pkt-len = 4*(HEXDIG)
64 | pktLenSize = 4
65 | )
66 |
67 | type state uint8
68 |
69 | const (
70 | ready state = iota
71 | readingLen
72 | readingPayload
73 | done
74 | )
75 |
76 | // step moves the state machine to the next state.
77 | // buf must contain all the data ready for consumption for current state.
78 | // It must not be called when state is done.
79 | func (p *pktLineParser) step() error {
80 | switch p.state {
81 | case ready:
82 | p.state = readingLen
83 | p.next = pktLenSize
84 | return nil
85 | case readingLen:
86 | // len(p.buf) is 4.
87 | pktLen, err := parsePktLen(p.buf)
88 | if err != nil {
89 | return err
90 | }
91 |
92 | switch {
93 | case pktLen == 0:
94 | p.state = done
95 | p.next = 0
96 | p.buf = nil
97 | return nil
98 | default:
99 | p.state = readingPayload
100 | p.next = pktLen - pktLenSize // (pkt-len - 4)*(OCTET)
101 | p.buf = p.buf[:0]
102 | return nil
103 | }
104 | case readingPayload:
105 | p.state = readingLen
106 | p.next = pktLenSize
107 | p.Lines = append(p.Lines, string(p.buf))
108 | p.buf = p.buf[:0]
109 | return nil
110 | default:
111 | panic(fmt.Errorf("unreachable: %v", p.state))
112 | }
113 | }
114 |
115 | // parsePktLen parses a pkt-len segment.
116 | // len(b) must be 4.
117 | func parsePktLen(b []byte) (int, error) {
118 | pktLen, err := parseHex(b)
119 | switch {
120 | case err != nil:
121 | return 0, err
122 | case 1 <= pktLen && pktLen < pktLenSize:
123 | return 0, fmt.Errorf("invalid pkt-len: %v", pktLen)
124 | case pktLen > 65524:
125 | // The maximum length of a pkt-line is 65524 bytes (65520 bytes of payload + 4 bytes of length data).
126 | return 0, fmt.Errorf("invalid pkt-len: %v", pktLen)
127 | }
128 | return int(pktLen), nil
129 | }
130 |
131 | // parseHex parses a 4-byte hex number.
132 | // len(h) must be 4.
133 | func parseHex(h []byte) (uint16, error) {
134 | var b [2]uint8
135 | n, err := hex.Decode(b[:], h)
136 | switch {
137 | case err != nil:
138 | return 0, err
139 | case n != 2:
140 | return 0, errors.New("short output")
141 | }
142 | return uint16(b[0])<<8 | uint16(b[1]), nil
143 | }
144 |
--------------------------------------------------------------------------------
/pkg/go-git-http/pktparser_test.go:
--------------------------------------------------------------------------------
1 | package githttp
2 |
3 | import (
4 | "encoding/hex"
5 | "errors"
6 | "reflect"
7 | "testing"
8 | )
9 |
10 | func TestParsePktLen(t *testing.T) {
11 | tests := []struct {
12 | in string
13 |
14 | wantLen int
15 | wantErr error
16 | }{
17 | // Valid pkt-len.
18 | {"00a5", 165, nil},
19 | {"01a5", 421, nil},
20 | {"0032", 50, nil},
21 | {"000b", 11, nil},
22 | {"000B", 11, nil},
23 |
24 | // Valud flush-pkt.
25 | {"0000", 0, nil},
26 |
27 | {"0001", 0, errors.New("invalid pkt-len: 1")},
28 | {"0003", 0, errors.New("invalid pkt-len: 3")},
29 | {"abyz", 0, hex.InvalidByteError('y')},
30 | {"-<%^", 0, hex.InvalidByteError('-')},
31 |
32 | // Maximum length.
33 | {"fff4", 65524, nil},
34 | {"fff5", 0, errors.New("invalid pkt-len: 65525")},
35 | {"ffff", 0, errors.New("invalid pkt-len: 65535")},
36 | }
37 |
38 | for _, tt := range tests {
39 | gotLen, gotErr := parsePktLen([]byte(tt.in))
40 | if gotLen != tt.wantLen || !reflect.DeepEqual(gotErr, tt.wantErr) {
41 | t.Errorf("test %q:\n got: %#v, %#v\nwant: %#v, %#v\n", tt.in, gotLen, gotErr, tt.wantLen, tt.wantErr)
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/pkg/go-git-http/routing.go:
--------------------------------------------------------------------------------
1 | package githttp
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 | "os"
7 | "regexp"
8 | "strings"
9 | )
10 |
11 | type Service struct {
12 | Method string
13 | Handler func(HandlerReq) error
14 | Rpc string
15 | }
16 |
17 | type HandlerReq struct {
18 | w http.ResponseWriter
19 | r *http.Request
20 | Rpc string
21 | Dir string
22 | File string
23 | }
24 |
25 | // Routing regexes
26 | var (
27 | _serviceRpcUpload = regexp.MustCompile("git/(.*?)/git-upload-pack$")
28 | _serviceRpcReceive = regexp.MustCompile("git/(.*?)/git-receive-pack$")
29 | _getInfoRefs = regexp.MustCompile("git/(.*?)/info/refs$")
30 | _getHead = regexp.MustCompile("git/(.*?)/HEAD$")
31 | _getAlternates = regexp.MustCompile("git/(.*?)/objects/info/alternates$")
32 | _getHttpAlternates = regexp.MustCompile("git/(.*?)/objects/info/http-alternates$")
33 | _getInfoPacks = regexp.MustCompile("git/(.*?)/objects/info/packs$")
34 | _getInfoFile = regexp.MustCompile("git/(.*?)/objects/info/[^/]*$")
35 | _getLooseObject = regexp.MustCompile("git/(.*?)/objects/[0-9a-f]{2}/[0-9a-f]{38}$")
36 | _getPackFile = regexp.MustCompile("git/(.*?)/objects/pack/pack-[0-9a-f]{40}\\.pack$")
37 | _getIdxFile = regexp.MustCompile("git/(.*?)/objects/pack/pack-[0-9a-f]{40}\\.idx$")
38 | )
39 |
40 | func (g *GitHttp) services() map[*regexp.Regexp]Service {
41 | return map[*regexp.Regexp]Service{
42 | _serviceRpcUpload: Service{"POST", g.serviceRpc, "upload-pack"},
43 | _serviceRpcReceive: Service{"POST", g.serviceRpc, "receive-pack"},
44 | _getInfoRefs: Service{"GET", g.getInfoRefs, ""},
45 | _getHead: Service{"GET", g.getTextFile, ""},
46 | _getAlternates: Service{"GET", g.getTextFile, ""},
47 | _getHttpAlternates: Service{"GET", g.getTextFile, ""},
48 | _getInfoPacks: Service{"GET", g.getInfoPacks, ""},
49 | _getInfoFile: Service{"GET", g.getTextFile, ""},
50 | _getLooseObject: Service{"GET", g.getLooseObject, ""},
51 | _getPackFile: Service{"GET", g.getPackFile, ""},
52 | _getIdxFile: Service{"GET", g.getIdxFile, ""},
53 | }
54 | }
55 |
56 | // getService return's the service corresponding to the
57 | // current http.Request's URL
58 | // as well as the name of the repo
59 | func (g *GitHttp) getService(path string) (string, *Service) {
60 | for re, service := range g.services() {
61 | if m := re.FindStringSubmatch(path); m != nil {
62 | return m[1], &service
63 | }
64 | }
65 |
66 | // No match
67 | return "", nil
68 | }
69 |
70 | // Request handling function
71 | func (g *GitHttp) requestHandler(w http.ResponseWriter, r *http.Request) {
72 | // Get service for URL
73 | repo, service := g.getService(r.URL.Path)
74 |
75 | fmt.Println("git handler", r.URL.Path, repo)
76 |
77 | // No url match
78 | if service == nil {
79 | renderNotFound(w)
80 | return
81 | }
82 |
83 | // Bad method
84 | if service.Method != r.Method {
85 | renderMethodNotAllowed(w, r)
86 | return
87 | }
88 |
89 | // Rpc type
90 | rpc := service.Rpc
91 |
92 | // Get specific file
93 | file := strings.Replace(r.URL.Path, repo+"/", "", 1)
94 |
95 | // Resolve directory
96 | dir, err := g.getGitDir(repo)
97 |
98 | // Repo not found on disk
99 | if err != nil {
100 | renderNotFound(w)
101 | return
102 | }
103 |
104 | // Build request info for handler
105 | hr := HandlerReq{w, r, rpc, dir, file}
106 |
107 | // Call handler
108 | if err := service.Handler(hr); err != nil {
109 | if os.IsNotExist(err) {
110 | renderNotFound(w)
111 | return
112 | }
113 | switch err.(type) {
114 | case *ErrorNoAccess:
115 | renderNoAccess(w)
116 | return
117 | }
118 | http.Error(w, err.Error(), 500)
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/pkg/go-git-http/rpc_reader.go:
--------------------------------------------------------------------------------
1 | package githttp
2 |
3 | import (
4 | "io"
5 | "regexp"
6 | "strings"
7 | )
8 |
9 | // RpcReader scans for events in the incoming rpc request data.
10 | type RpcReader struct {
11 | // Underlying reader (to relay calls to).
12 | io.Reader
13 |
14 | // Rpc type (receive-pack or upload-pack).
15 | Rpc string
16 |
17 | // List of events RpcReader has picked up through scanning.
18 | // These events do not have the Dir field set.
19 | Events []Event
20 |
21 | pktLineParser pktLineParser
22 | }
23 |
24 | // Read implements the io.Reader interface.
25 | func (r *RpcReader) Read(p []byte) (n int, err error) {
26 | // Relay call
27 | n, err = r.Reader.Read(p)
28 |
29 | // Scan for events
30 | if n > 0 {
31 | r.scan(p[:n])
32 | }
33 |
34 | return n, err
35 | }
36 |
37 | func (r *RpcReader) scan(data []byte) {
38 | if r.pktLineParser.state == done {
39 | return
40 | }
41 |
42 | r.pktLineParser.Feed(data)
43 |
44 | // If parsing has just finished, process its output once.
45 | if r.pktLineParser.state == done {
46 | if r.pktLineParser.Error != nil {
47 | return
48 | }
49 |
50 | // When we get here, we're done collecting all pkt-lines successfully
51 | // and can now extract relevant events.
52 | switch r.Rpc {
53 | case "receive-pack":
54 | for _, line := range r.pktLineParser.Lines {
55 | events := scanPush(line)
56 | r.Events = append(r.Events, events...)
57 | }
58 | case "upload-pack":
59 | total := strings.Join(r.pktLineParser.Lines, "")
60 | events := scanFetch(total)
61 | r.Events = append(r.Events, events...)
62 | }
63 | }
64 | }
65 |
66 | // TODO: Avoid using regexp to parse a well documented binary protocol with an open source
67 | // implementation. There should not be a need for regexp.
68 |
69 | // receivePackRegex is used once per pkt-line.
70 | var receivePackRegex = regexp.MustCompile("([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) refs\\/(heads|tags)\\/(.+?)(\x00|$)")
71 |
72 | func scanPush(line string) []Event {
73 | matches := receivePackRegex.FindAllStringSubmatch(line, -1)
74 |
75 | if matches == nil {
76 | return nil
77 | }
78 |
79 | var events []Event
80 | for _, m := range matches {
81 | e := Event{
82 | Last: m[1],
83 | Commit: m[2],
84 | }
85 |
86 | // Handle pushes to branches and tags differently
87 | if m[3] == "heads" {
88 | e.Type = PUSH
89 | e.Branch = m[4]
90 | } else {
91 | e.Type = TAG
92 | e.Tag = m[4]
93 | }
94 |
95 | events = append(events, e)
96 | }
97 |
98 | return events
99 | }
100 |
101 | // uploadPackRegex is used once on the entire header data.
102 | var uploadPackRegex = regexp.MustCompile(`^want ([0-9a-fA-F]{40})`)
103 |
104 | func scanFetch(total string) []Event {
105 | matches := uploadPackRegex.FindAllStringSubmatch(total, -1)
106 |
107 | if matches == nil {
108 | return nil
109 | }
110 |
111 | var events []Event
112 | for _, m := range matches {
113 | events = append(events, Event{
114 | Type: FETCH,
115 | Commit: m[1],
116 | })
117 | }
118 |
119 | return events
120 | }
121 |
--------------------------------------------------------------------------------
/pkg/go-git-http/rpc_reader_test.go:
--------------------------------------------------------------------------------
1 | package githttp_test
2 |
3 | import (
4 | "io"
5 | "io/ioutil"
6 | "net/http"
7 | "os"
8 | "path/filepath"
9 | "reflect"
10 | "testing"
11 | )
12 |
13 | func TestRpcReader(t *testing.T) {
14 | tests := []struct {
15 | rpc string
16 | file string
17 |
18 | want []githttp.Event
19 | }{
20 | {
21 | rpc: "receive-pack",
22 | file: "receive-pack.0",
23 |
24 | want: []githttp.Event{
25 | (githttp.Event)(githttp.Event{
26 | Type: (githttp.EventType)(githttp.PUSH),
27 | Commit: (string)("92eef6dcb9cc198bc3ac6010c108fa482773f116"),
28 | Dir: (string)(""),
29 | Tag: (string)(""),
30 | Last: (string)("0000000000000000000000000000000000000000"),
31 | Branch: (string)("master"),
32 | Error: (error)(nil),
33 | Request: (*http.Request)(nil),
34 | }),
35 | },
36 | },
37 |
38 | // A tag using letters only.
39 | {
40 | rpc: "receive-pack",
41 | file: "receive-pack.1",
42 |
43 | want: []githttp.Event{
44 | (githttp.Event)(githttp.Event{
45 | Type: (githttp.EventType)(githttp.TAG),
46 | Commit: (string)("3da295397738f395c2ca5fd5570f01a9fcea3be3"),
47 | Dir: (string)(""),
48 | Tag: (string)("sometextualtag"),
49 | Last: (string)("0000000000000000000000000000000000000000"),
50 | Branch: (string)(""),
51 | Error: (error)(nil),
52 | Request: (*http.Request)(nil),
53 | }),
54 | },
55 | },
56 |
57 | // A tag containing the string "00".
58 | {
59 | rpc: "receive-pack",
60 | file: "receive-pack.2",
61 |
62 | want: []githttp.Event{
63 | (githttp.Event)(githttp.Event{
64 | Type: (githttp.EventType)(githttp.TAG),
65 | Commit: (string)("3da295397738f395c2ca5fd5570f01a9fcea3be3"),
66 | Dir: (string)(""),
67 | Tag: (string)("1.000.1"),
68 | Last: (string)("0000000000000000000000000000000000000000"),
69 | Branch: (string)(""),
70 | Error: (error)(nil),
71 | Request: (*http.Request)(nil),
72 | }),
73 | },
74 | },
75 |
76 | // Multiple tags containing string "00" in one git push operation.
77 | {
78 | rpc: "receive-pack",
79 | file: "receive-pack.3",
80 |
81 | want: []githttp.Event{
82 | (githttp.Event)(githttp.Event{
83 | Type: (githttp.EventType)(githttp.TAG),
84 | Commit: (string)("3da295397738f395c2ca5fd5570f01a9fcea3be3"),
85 | Dir: (string)(""),
86 | Tag: (string)("1.000.2"),
87 | Last: (string)("0000000000000000000000000000000000000000"),
88 | Branch: (string)(""),
89 | Error: (error)(nil),
90 | Request: (*http.Request)(nil),
91 | }),
92 | (githttp.Event)(githttp.Event{
93 | Type: (githttp.EventType)(githttp.TAG),
94 | Commit: (string)("3da295397738f395c2ca5fd5570f01a9fcea3be3"),
95 | Dir: (string)(""),
96 | Tag: (string)("1.000.3"),
97 | Last: (string)("0000000000000000000000000000000000000000"),
98 | Branch: (string)(""),
99 | Error: (error)(nil),
100 | Request: (*http.Request)(nil),
101 | }),
102 | (githttp.Event)(githttp.Event{
103 | Type: (githttp.EventType)(githttp.TAG),
104 | Commit: (string)("3da295397738f395c2ca5fd5570f01a9fcea3be3"),
105 | Dir: (string)(""),
106 | Tag: (string)("1.000.4"),
107 | Last: (string)("0000000000000000000000000000000000000000"),
108 | Branch: (string)(""),
109 | Error: (error)(nil),
110 | Request: (*http.Request)(nil),
111 | }),
112 | },
113 | },
114 |
115 | {
116 | rpc: "upload-pack",
117 | file: "upload-pack.0",
118 |
119 | want: []githttp.Event{
120 | (githttp.Event)(githttp.Event{
121 | Type: (githttp.EventType)(githttp.FETCH),
122 | Commit: (string)("a647ec2ea40ee9ca35d32232dc28de22b1537e00"),
123 | Dir: (string)(""),
124 | Tag: (string)(""),
125 | Last: (string)(""),
126 | Branch: (string)(""),
127 | Error: (error)(nil),
128 | Request: (*http.Request)(nil),
129 | }),
130 | },
131 | },
132 |
133 | {
134 | rpc: "upload-pack",
135 | file: "upload-pack.1",
136 |
137 | want: []githttp.Event{
138 | (githttp.Event)(githttp.Event{
139 | Type: (githttp.EventType)(githttp.FETCH),
140 | Commit: (string)("92eef6dcb9cc198bc3ac6010c108fa482773f116"),
141 | Dir: (string)(""),
142 | Tag: (string)(""),
143 | Last: (string)(""),
144 | Branch: (string)(""),
145 | Error: (error)(nil),
146 | Request: (*http.Request)(nil),
147 | }),
148 | },
149 | },
150 | }
151 |
152 | for _, tt := range tests {
153 | f, err := os.Open(filepath.Join("testdata", tt.file))
154 | if err != nil {
155 | t.Fatal(err)
156 | }
157 |
158 | r := fragmentedReader{f}
159 |
160 | rr := &githttp.RpcReader{
161 | Reader: r,
162 | Rpc: tt.rpc,
163 | }
164 |
165 | _, err = io.Copy(ioutil.Discard, rr)
166 | if err != nil {
167 | t.Errorf("io.Copy: %v", err)
168 | }
169 |
170 | f.Close()
171 |
172 | if got := rr.Events; !reflect.DeepEqual(got, tt.want) {
173 | t.Errorf("test %q/%q:\n got: %#v\nwant: %#v\n", tt.rpc, tt.file, got, tt.want)
174 | }
175 | }
176 | }
177 |
178 | // fragmentedReader reads from R, with each Read call returning at most fragmentLen bytes even
179 | // if len(p) is greater than fragmentLen.
180 | // It purposefully adds a layer of inefficiency around R, and exists for testing purposes only.
181 | type fragmentedReader struct {
182 | R io.Reader // Underlying reader.
183 | }
184 |
185 | func (r fragmentedReader) Read(p []byte) (n int, err error) {
186 | const fragmentLen = 1
187 | if len(p) <= fragmentLen {
188 | return r.R.Read(p)
189 | }
190 | return r.R.Read(p[:fragmentLen])
191 | }
192 |
--------------------------------------------------------------------------------
/pkg/go-git-http/testdata/receive-pack.0:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/honza/smithy/8a44b970ddcf6a38a6d9013f44b1ec3ca14500a3/pkg/go-git-http/testdata/receive-pack.0
--------------------------------------------------------------------------------
/pkg/go-git-http/testdata/receive-pack.1:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/honza/smithy/8a44b970ddcf6a38a6d9013f44b1ec3ca14500a3/pkg/go-git-http/testdata/receive-pack.1
--------------------------------------------------------------------------------
/pkg/go-git-http/testdata/receive-pack.2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/honza/smithy/8a44b970ddcf6a38a6d9013f44b1ec3ca14500a3/pkg/go-git-http/testdata/receive-pack.2
--------------------------------------------------------------------------------
/pkg/go-git-http/testdata/receive-pack.3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/honza/smithy/8a44b970ddcf6a38a6d9013f44b1ec3ca14500a3/pkg/go-git-http/testdata/receive-pack.3
--------------------------------------------------------------------------------
/pkg/go-git-http/testdata/upload-pack.0:
--------------------------------------------------------------------------------
1 | 0092want a647ec2ea40ee9ca35d32232dc28de22b1537e00 multi_ack_detailed side-band-64k thin-pack include-tag ofs-delta agent=git/2.5.4.(Apple.Git-61)
2 | 00000032have 92eef6dcb9cc198bc3ac6010c108fa482773f116
3 | 0032have 0e2a2938f51c984c4f6fe68400a77b2748e3aac7
4 | 0032have 9b40a23d8105a4d8f5babf9756742b353bc0bf86
5 | 0032have 6dcded2bb74142bd97b346c70e34a7a117dc359d
6 | 0032have 5e247562e60140946b2996ab428ea4ebfd8f7aef
7 | 0032have e399f2b29d0efe66a9597239abdbec5e2960e172
8 | 0032have 6779fd7460bf02ffa67fd7c6fa412fedc22eea02
9 | 0032have 30d62cac4ee3185b5670e9ca366a409a2dade471
10 | 0032have 088a47a7b0d7141b71dbf0fabfbad66c61ffb99f
11 | 0032have 2c960968453207a2a66309e2e752759888345900
12 | 0032have 4f0b8f6c5df1a903b204cdc9ff20a7e00873d73d
13 | 0032have d403d8b126c2d566eb105102972df356f7824406
14 | 0032have 926801b90aa8180679eccb0ee3231de10903df9b
15 | 0032have accbc2b1a251e2cd6dd0c3fba74c2f7789a5addd
16 | 0032have f6eb2af801c0722b3112346da3d1fd68e164cb74
17 | 0032have 5e3817ddb991f9530c9cfc7c4a5cf5203257fbd8
18 | 00000032have 5ee7e39b927366c74181b199e0da78467699dd3d
19 | 0032have 5d1d1ba532f180d55a6fc7b23bda8162693447d7
20 | 0032have 5dc05dfd1827333036a03b455267432157d8315a
21 | 0032have 3378b0e3808ce82185bdf57d97c5d5c7655f14d3
22 | 0032have 1aa139246412abf51c7f6402deb3d9ee84cfba6b
23 | 0032have e94b8b4c1c9103d4aa8656f55c54ce91f4941237
24 | 0032have 2edfc6f95708194e23c92bf80da115fd76953cdc
25 | 0032have 64b4dcae2edfdd6201efe622f6394694281c1fab
26 | 0032have 6224112203b656464ee55f42eca4b80a9d8ae854
27 | 0032have 230517d50257e5f8d2706976c7ed7d333e2b9916
28 | 0032have 6b8d66508e23b76ecf8236b45218c77d2e66c7df
29 | 0032have 622958e856b24f771218aad8d26264d403df0021
30 | 0032have 1c82218b4749a5eec2750b876a7544105d357db5
31 | 0032have 861ceef44614479fecb6c5118773afc73c22fc31
32 | 0032have 74b86980e2e8e3d47b58a719e854819cab1ffb8b
33 | 0032have bb5f1a2dbd16acb79584beb4021053c3718b07ce
34 | 00000009done
35 |
--------------------------------------------------------------------------------
/pkg/go-git-http/testdata/upload-pack.1:
--------------------------------------------------------------------------------
1 | 0086want 92eef6dcb9cc198bc3ac6010c108fa482773f116 multi_ack_detailed side-band-64k thin-pack ofs-delta agent=git/2.5.4.(Apple.Git-61)
2 | 0032want 92eef6dcb9cc198bc3ac6010c108fa482773f116
3 | 00000009done
4 |
--------------------------------------------------------------------------------
/pkg/go-git-http/utils.go:
--------------------------------------------------------------------------------
1 | package githttp
2 |
3 | import (
4 | "compress/flate"
5 | "compress/gzip"
6 | "fmt"
7 | "io"
8 | "net/http"
9 | "strconv"
10 | "strings"
11 | "time"
12 | )
13 |
14 | // requestReader returns an io.ReadCloser
15 | // that will decode data if needed, depending on the
16 | // "content-encoding" header
17 | func requestReader(req *http.Request) (io.ReadCloser, error) {
18 | switch req.Header.Get("content-encoding") {
19 | case "gzip":
20 | return gzip.NewReader(req.Body)
21 | case "deflate":
22 | return flate.NewReader(req.Body), nil
23 | }
24 |
25 | // If no encoding, use raw body
26 | return req.Body, nil
27 | }
28 |
29 | // HTTP parsing utility functions
30 |
31 | func getServiceType(r *http.Request) string {
32 | service_type := r.FormValue("service")
33 |
34 | if s := strings.HasPrefix(service_type, "git-"); !s {
35 | return ""
36 | }
37 |
38 | return strings.Replace(service_type, "git-", "", 1)
39 | }
40 |
41 | // HTTP error response handling functions
42 |
43 | func renderMethodNotAllowed(w http.ResponseWriter, r *http.Request) {
44 | if r.Proto == "HTTP/1.1" {
45 | w.WriteHeader(http.StatusMethodNotAllowed)
46 | w.Write([]byte("Method Not Allowed"))
47 | } else {
48 | w.WriteHeader(http.StatusBadRequest)
49 | w.Write([]byte("Bad Request"))
50 | }
51 | }
52 |
53 | func renderNotFound(w http.ResponseWriter) {
54 | w.WriteHeader(http.StatusNotFound)
55 | w.Write([]byte("Not Found"))
56 | }
57 |
58 | func renderNoAccess(w http.ResponseWriter) {
59 | w.WriteHeader(http.StatusForbidden)
60 | w.Write([]byte("Forbidden"))
61 | }
62 |
63 | // Packet-line handling function
64 |
65 | func packetFlush() []byte {
66 | return []byte("0000")
67 | }
68 |
69 | func packetWrite(str string) []byte {
70 | s := strconv.FormatInt(int64(len(str)+4), 16)
71 |
72 | if len(s)%4 != 0 {
73 | s = strings.Repeat("0", 4-len(s)%4) + s
74 | }
75 |
76 | return []byte(s + str)
77 | }
78 |
79 | // Header writing functions
80 |
81 | func hdrNocache(w http.ResponseWriter) {
82 | w.Header().Set("Expires", "Fri, 01 Jan 1980 00:00:00 GMT")
83 | w.Header().Set("Pragma", "no-cache")
84 | w.Header().Set("Cache-Control", "no-cache, max-age=0, must-revalidate")
85 | }
86 |
87 | func hdrCacheForever(w http.ResponseWriter) {
88 | now := time.Now().Unix()
89 | expires := now + 31536000
90 | w.Header().Set("Date", fmt.Sprintf("%d", now))
91 | w.Header().Set("Expires", fmt.Sprintf("%d", expires))
92 | w.Header().Set("Cache-Control", "public, max-age=31536000")
93 | }
94 |
--------------------------------------------------------------------------------
/pkg/go-git-http/version.go:
--------------------------------------------------------------------------------
1 | package githttp
2 |
3 | const VERSION = "1.0.0"
4 |
--------------------------------------------------------------------------------
/pkg/smithy/config.go:
--------------------------------------------------------------------------------
1 | // smithy --- the git forge
2 | // Copyright (C) 2020 Honza Pokorny
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU General Public License
15 | // along with this program. If not, see .
16 |
17 | package smithy
18 |
19 | import (
20 | "fmt"
21 | "io/ioutil"
22 | "path"
23 | "path/filepath"
24 | "sort"
25 |
26 | "github.com/go-git/go-git/v5"
27 | "gopkg.in/yaml.v2"
28 | )
29 |
30 | type RepoConfig struct {
31 | Path string
32 | Slug string
33 | Title string
34 | Description string
35 | Exclude bool
36 | }
37 |
38 | type GitConfig struct {
39 | Root string `yaml:"root"`
40 | Repos []RepoConfig `yaml:",omitempty"`
41 |
42 | // ReposBySlug is an extrapolaed value
43 | reposBySlug map[string]RepositoryWithName
44 |
45 | // staticReposBySlug is a map of the `repos` values
46 | staticReposBySlug map[string]RepoConfig
47 | }
48 |
49 | type StaticConfig struct {
50 | Root string
51 | Prefix string
52 | }
53 |
54 | type SmithyConfig struct {
55 | Title string `yaml:"title"`
56 | Description string `yaml:"description"`
57 | Host string `yaml:"host"`
58 | Git GitConfig
59 | Static StaticConfig
60 | Templates struct {
61 | Dir string
62 | }
63 | Port int `yaml:"port"`
64 | }
65 |
66 | func (sc *SmithyConfig) findStaticRepo(slug string) (RepoConfig, bool) {
67 | value, exists := sc.Git.staticReposBySlug[slug]
68 | return value, exists
69 | }
70 |
71 | func (sc *SmithyConfig) FindRepo(slug string) (RepositoryWithName, bool) {
72 | value, exists := sc.Git.reposBySlug[slug]
73 | return value, exists
74 | }
75 |
76 | func (sc *SmithyConfig) GetRepositories() []RepositoryWithName {
77 | var repos []RepositoryWithName
78 |
79 | for _, repo := range sc.Git.reposBySlug {
80 | repos = append(repos, repo)
81 | }
82 |
83 | sort.Sort(RepositoryByName(repos))
84 | return repos
85 | }
86 |
87 | func (sc *SmithyConfig) LoadAllRepositories() error {
88 | sc.Git.staticReposBySlug = make(map[string]RepoConfig)
89 |
90 | for _, repo := range sc.Git.Repos {
91 | k := repo.Path
92 | if repo.Slug != "" {
93 | k = repo.Slug
94 | }
95 | sc.Git.staticReposBySlug[k] = repo
96 | }
97 |
98 | repos, err := ioutil.ReadDir(sc.Git.Root)
99 |
100 | if err != nil {
101 | return err
102 | }
103 |
104 | // TODO: should we clear out or not?
105 | sc.Git.reposBySlug = make(map[string]RepositoryWithName)
106 |
107 | for _, repo := range repos {
108 | repoObj, exists := sc.findStaticRepo(repo.Name())
109 |
110 | if exists == true && repoObj.Exclude == true {
111 | continue
112 | }
113 |
114 | repoPath := path.Join(sc.Git.Root, repo.Name())
115 |
116 | r, err := git.PlainOpen(repoPath)
117 | if err != nil {
118 | // Ignore directories that aren't git repositories
119 | continue
120 | }
121 |
122 | rwn := RepositoryWithName{Name: repo.Name(), Repository: r}
123 | key := repo.Name()
124 |
125 | if exists {
126 | rwn.Meta = repoObj
127 | rwn.Name = repoObj.Title
128 |
129 | if repoObj.Slug != "" {
130 | key = repoObj.Slug
131 | }
132 | }
133 |
134 | sc.Git.reposBySlug[key] = rwn
135 |
136 | }
137 |
138 | for _, repo := range sc.Git.Repos {
139 | if repo.Exclude == true {
140 | continue
141 | }
142 |
143 | if !filepath.IsAbs(repo.Path) {
144 | continue
145 | }
146 |
147 | r, err := git.PlainOpen(repo.Path)
148 | if err != nil {
149 | // Ignore directories that aren't git repositories
150 | continue
151 | }
152 | rwn := RepositoryWithName{Name: repo.Title, Repository: r, Meta: repo}
153 | key := repo.Path
154 | if repo.Slug != "" {
155 | key = repo.Slug
156 | }
157 |
158 | sc.Git.reposBySlug[key] = rwn
159 | }
160 |
161 | return nil
162 |
163 | }
164 |
165 | func LoadConfig(path string) (SmithyConfig, error) {
166 | var smithyConfig SmithyConfig
167 |
168 | if path == "" {
169 | path = "config.yaml"
170 | }
171 |
172 | contents, err := ioutil.ReadFile(path)
173 |
174 | if err != nil {
175 | return smithyConfig, err
176 | }
177 |
178 | err = yaml.Unmarshal(contents, &smithyConfig)
179 |
180 | if err != nil {
181 | return smithyConfig, err
182 | }
183 |
184 | err = smithyConfig.LoadAllRepositories()
185 |
186 | if err != nil {
187 | return smithyConfig, err
188 | }
189 |
190 | return smithyConfig, nil
191 | }
192 |
193 | func New() SmithyConfig {
194 | return SmithyConfig{
195 | Title: "Smithy, a lightweight git force",
196 | Port: 3456,
197 | Host: "localhost",
198 | Description: "Publish your git repositories with ease",
199 | Static: StaticConfig{
200 | Prefix: "/static/",
201 | },
202 | }
203 | }
204 |
205 | func GenerateDefaultConfig() {
206 | config := New()
207 | out, _ := yaml.Marshal(config)
208 | fmt.Print(string(out))
209 | }
210 |
--------------------------------------------------------------------------------
/pkg/smithy/encoder.go:
--------------------------------------------------------------------------------
1 | // smithy --- the git forge
2 | // Copyright (C) 2020 Honza Pokorny
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU General Public License
15 | // along with this program. If not, see .
16 |
17 | // This file is largely based on
18 | // https://github.com/go-git/go-git/blob/70111361e674d786d3e8fca494229d0ad8361de9/plumbing/format/diff/unified_encoder.go
19 | // Original code licensed under Apache 2.0
20 | package smithy
21 |
22 | import (
23 | "fmt"
24 | "html"
25 | "io"
26 | "regexp"
27 | "strconv"
28 | "strings"
29 |
30 | "github.com/go-git/go-git/v5/plumbing"
31 | "github.com/go-git/go-git/v5/plumbing/format/diff"
32 | "github.com/go-git/go-git/v5/plumbing/object"
33 | )
34 |
35 | // DefaultContextLines is the default number of context lines.
36 | const DefaultContextLines = 3
37 |
38 | var (
39 | splitLinesRegexp = regexp.MustCompile(`[^\n]*(\n|$)`)
40 |
41 | operationChar = map[diff.Operation]byte{
42 | diff.Add: '+',
43 | diff.Delete: '-',
44 | diff.Equal: ' ',
45 | }
46 | operationClass = map[diff.Operation]string{
47 | diff.Add: "diff-add",
48 | diff.Delete: "diff-delete",
49 | diff.Equal: "diff-equal",
50 | }
51 | )
52 |
53 | // UnifiedEncoder encodes an unified diff into the provided Writer. It does not
54 | // support similarity index for renames or sorting hash representations.
55 | type UnifiedEncoder struct {
56 | io.Writer
57 |
58 | // contextLines is the count of unchanged lines that will appear surrounding
59 | // a change.
60 | contextLines int
61 | }
62 |
63 | // NewUnifiedEncoder returns a new UnifiedEncoder that writes to w.
64 | func NewUnifiedEncoder(w io.Writer, contextLines int) *UnifiedEncoder {
65 | return &UnifiedEncoder{
66 | Writer: w,
67 | contextLines: contextLines,
68 | }
69 | }
70 |
71 | // Encode encodes patch.
72 | func (e *UnifiedEncoder) Encode(patch object.Patch) error {
73 | sb := &strings.Builder{}
74 |
75 | if message := patch.Message(); message != "" {
76 | sb.WriteString(message)
77 | if !strings.HasSuffix(message, "\n") {
78 | sb.WriteByte('\n')
79 | }
80 | }
81 |
82 | for _, filePatch := range patch.FilePatches() {
83 | e.writeFilePatchHeader(sb, filePatch)
84 | g := newHunksGenerator(filePatch.Chunks(), e.contextLines)
85 | for _, hunk := range g.Generate() {
86 | hunk.writeTo(sb)
87 | }
88 | }
89 |
90 | _, err := e.Write([]byte(sb.String()))
91 | return err
92 | }
93 |
94 | func (e *UnifiedEncoder) writeFilePatchHeader(sb *strings.Builder, filePatch diff.FilePatch) {
95 | from, to := filePatch.Files()
96 | if from == nil && to == nil {
97 | return
98 | }
99 | isBinary := filePatch.IsBinary()
100 |
101 | var lines []string
102 | switch {
103 | case from != nil && to != nil:
104 | hashEquals := from.Hash() == to.Hash()
105 | lines = append(lines,
106 | fmt.Sprintf("diff --git a/%s b/%s", from.Path(), to.Path()),
107 | )
108 | if from.Mode() != to.Mode() {
109 | lines = append(lines,
110 | fmt.Sprintf("old mode %o", from.Mode()),
111 | fmt.Sprintf("new mode %o", to.Mode()),
112 | )
113 | }
114 | if from.Path() != to.Path() {
115 | lines = append(lines,
116 | fmt.Sprintf("rename from %s", from.Path()),
117 | fmt.Sprintf("rename to %s", to.Path()),
118 | )
119 | }
120 | if from.Mode() != to.Mode() && !hashEquals {
121 | lines = append(lines,
122 | fmt.Sprintf("index %s..%s", from.Hash(), to.Hash()),
123 | )
124 | } else if !hashEquals {
125 | lines = append(lines,
126 | fmt.Sprintf("index %s..%s %o", from.Hash(), to.Hash(), from.Mode()),
127 | )
128 | }
129 | if !hashEquals {
130 | lines = e.appendPathLines(lines, "a/"+from.Path(), "b/"+to.Path(), isBinary)
131 | }
132 | case from == nil:
133 | lines = append(lines,
134 | fmt.Sprintf("diff --git a/%s b/%s", to.Path(), to.Path()),
135 | fmt.Sprintf("new file mode %o", to.Mode()),
136 | fmt.Sprintf("index %s..%s", plumbing.ZeroHash, to.Hash()),
137 | )
138 | lines = e.appendPathLines(lines, "/dev/null", "b/"+to.Path(), isBinary)
139 | case to == nil:
140 | lines = append(lines,
141 | fmt.Sprintf("diff --git a/%s b/%s", from.Path(), from.Path()),
142 | fmt.Sprintf("deleted file mode %o", from.Mode()),
143 | fmt.Sprintf("index %s..%s", from.Hash(), plumbing.ZeroHash),
144 | )
145 | lines = e.appendPathLines(lines, "a/"+from.Path(), "/dev/null", isBinary)
146 | }
147 |
148 | sb.WriteString(lines[0])
149 | for _, line := range lines[1:] {
150 | sb.WriteByte('\n')
151 | sb.WriteString(line)
152 | }
153 | sb.WriteByte('\n')
154 | }
155 |
156 | func (e *UnifiedEncoder) appendPathLines(lines []string, fromPath, toPath string, isBinary bool) []string {
157 | if isBinary {
158 | return append(lines,
159 | fmt.Sprintf("Binary files %s and %s differ", fromPath, toPath),
160 | )
161 | }
162 | return append(lines,
163 | fmt.Sprintf("--- %s", fromPath),
164 | fmt.Sprintf("+++ %s", toPath),
165 | )
166 | }
167 |
168 | type hunksGenerator struct {
169 | fromLine, toLine int
170 | ctxLines int
171 | chunks []diff.Chunk
172 | current *hunk
173 | hunks []*hunk
174 | beforeContext, afterContext []string
175 | }
176 |
177 | func newHunksGenerator(chunks []diff.Chunk, ctxLines int) *hunksGenerator {
178 | return &hunksGenerator{
179 | chunks: chunks,
180 | ctxLines: ctxLines,
181 | }
182 | }
183 |
184 | func (g *hunksGenerator) Generate() []*hunk {
185 | for i, chunk := range g.chunks {
186 | lines := splitLines(chunk.Content())
187 | nLines := len(lines)
188 |
189 | switch chunk.Type() {
190 | case diff.Equal:
191 | g.fromLine += nLines
192 | g.toLine += nLines
193 | g.processEqualsLines(lines, i)
194 | case diff.Delete:
195 | if nLines != 0 {
196 | g.fromLine++
197 | }
198 |
199 | g.processHunk(i, chunk.Type())
200 | g.fromLine += nLines - 1
201 | g.current.AddOp(chunk.Type(), lines...)
202 | case diff.Add:
203 | if nLines != 0 {
204 | g.toLine++
205 | }
206 | g.processHunk(i, chunk.Type())
207 | g.toLine += nLines - 1
208 | g.current.AddOp(chunk.Type(), lines...)
209 | }
210 |
211 | if i == len(g.chunks)-1 && g.current != nil {
212 | g.hunks = append(g.hunks, g.current)
213 | }
214 | }
215 |
216 | return g.hunks
217 | }
218 |
219 | func (g *hunksGenerator) processHunk(i int, op diff.Operation) {
220 | if g.current != nil {
221 | return
222 | }
223 |
224 | var ctxPrefix string
225 | linesBefore := len(g.beforeContext)
226 | if linesBefore > g.ctxLines {
227 | ctxPrefix = g.beforeContext[linesBefore-g.ctxLines-1]
228 | g.beforeContext = g.beforeContext[linesBefore-g.ctxLines:]
229 | linesBefore = g.ctxLines
230 | }
231 |
232 | g.current = &hunk{ctxPrefix: strings.TrimSuffix(ctxPrefix, "\n")}
233 | g.current.AddOp(diff.Equal, g.beforeContext...)
234 |
235 | switch op {
236 | case diff.Delete:
237 | g.current.fromLine, g.current.toLine =
238 | g.addLineNumbers(g.fromLine, g.toLine, linesBefore, i, diff.Add)
239 | case diff.Add:
240 | g.current.toLine, g.current.fromLine =
241 | g.addLineNumbers(g.toLine, g.fromLine, linesBefore, i, diff.Delete)
242 | }
243 |
244 | g.beforeContext = nil
245 | }
246 |
247 | // addLineNumbers obtains the line numbers in a new chunk.
248 | func (g *hunksGenerator) addLineNumbers(la, lb int, linesBefore int, i int, op diff.Operation) (cla, clb int) {
249 | cla = la - linesBefore
250 | // we need to search for a reference for the next diff
251 | switch {
252 | case linesBefore != 0 && g.ctxLines != 0:
253 | if lb > g.ctxLines {
254 | clb = lb - g.ctxLines + 1
255 | } else {
256 | clb = 1
257 | }
258 | case g.ctxLines == 0:
259 | clb = lb
260 | case i != len(g.chunks)-1:
261 | next := g.chunks[i+1]
262 | if next.Type() == op || next.Type() == diff.Equal {
263 | // this diff will be into this chunk
264 | clb = lb + 1
265 | }
266 | }
267 |
268 | return
269 | }
270 |
271 | func (g *hunksGenerator) processEqualsLines(ls []string, i int) {
272 | if g.current == nil {
273 | g.beforeContext = append(g.beforeContext, ls...)
274 | return
275 | }
276 |
277 | g.afterContext = append(g.afterContext, ls...)
278 | if len(g.afterContext) <= g.ctxLines*2 && i != len(g.chunks)-1 {
279 | g.current.AddOp(diff.Equal, g.afterContext...)
280 | g.afterContext = nil
281 | } else {
282 | ctxLines := g.ctxLines
283 | if ctxLines > len(g.afterContext) {
284 | ctxLines = len(g.afterContext)
285 | }
286 | g.current.AddOp(diff.Equal, g.afterContext[:ctxLines]...)
287 | g.hunks = append(g.hunks, g.current)
288 |
289 | g.current = nil
290 | g.beforeContext = g.afterContext[ctxLines:]
291 | g.afterContext = nil
292 | }
293 | }
294 |
295 | func splitLines(s string) []string {
296 | out := splitLinesRegexp.FindAllString(s, -1)
297 | if out[len(out)-1] == "" {
298 | out = out[:len(out)-1]
299 | }
300 | return out
301 | }
302 |
303 | type hunk struct {
304 | fromLine int
305 | toLine int
306 |
307 | fromCount int
308 | toCount int
309 |
310 | ctxPrefix string
311 | ops []*op
312 | }
313 |
314 | func (h *hunk) writeTo(sb *strings.Builder) {
315 | sb.WriteString("@@ -")
316 |
317 | if h.fromCount == 1 {
318 | sb.WriteString(strconv.Itoa(h.fromLine))
319 | } else {
320 | sb.WriteString(strconv.Itoa(h.fromLine))
321 | sb.WriteByte(',')
322 | sb.WriteString(strconv.Itoa(h.fromCount))
323 | }
324 |
325 | sb.WriteString(" +")
326 |
327 | if h.toCount == 1 {
328 | sb.WriteString(strconv.Itoa(h.toLine))
329 | } else {
330 | sb.WriteString(strconv.Itoa(h.toLine))
331 | sb.WriteByte(',')
332 | sb.WriteString(strconv.Itoa(h.toCount))
333 | }
334 |
335 | sb.WriteString(" @@")
336 |
337 | if h.ctxPrefix != "" {
338 | sb.WriteByte(' ')
339 | sb.WriteString(h.ctxPrefix)
340 | }
341 |
342 | sb.WriteByte('\n')
343 |
344 | for _, op := range h.ops {
345 | op.writeTo(sb)
346 | }
347 |
348 | }
349 |
350 | func (h *hunk) AddOp(t diff.Operation, ss ...string) {
351 | n := len(ss)
352 | switch t {
353 | case diff.Add:
354 | h.toCount += n
355 | case diff.Delete:
356 | h.fromCount += n
357 | case diff.Equal:
358 | h.toCount += n
359 | h.fromCount += n
360 | }
361 |
362 | for _, s := range ss {
363 | h.ops = append(h.ops, &op{s, t})
364 | }
365 | }
366 |
367 | type op struct {
368 | text string
369 | t diff.Operation
370 | }
371 |
372 | func esc(s string) string {
373 | return html.EscapeString(s)
374 | }
375 |
376 | func (o *op) writeTo(sb *strings.Builder) {
377 | sb.WriteString("")
380 | sb.WriteByte(operationChar[o.t])
381 | if strings.HasSuffix(o.text, "\n") {
382 | sb.WriteString(strings.TrimSuffix(esc(o.text), "\n"))
383 | } else {
384 | sb.WriteString(esc(o.text) + "\n\\ No newline at end of file")
385 | }
386 | sb.WriteString("")
387 | sb.WriteByte('\n')
388 | }
389 |
--------------------------------------------------------------------------------
/pkg/smithy/smithy.go:
--------------------------------------------------------------------------------
1 | // smithy --- the git forge
2 | // Copyright (C) 2020 Honza Pokorny
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU General Public License
15 | // along with this program. If not, see .
16 |
17 | package smithy
18 |
19 | import (
20 | "bytes"
21 | "errors"
22 | "fmt"
23 | "html/template"
24 | "io"
25 | "io/ioutil"
26 | "net/http"
27 | "os"
28 | "path/filepath"
29 | "regexp"
30 | "sort"
31 | "strings"
32 |
33 | "github.com/alecthomas/chroma/formatters/html"
34 | "github.com/alecthomas/chroma/lexers"
35 | "github.com/alecthomas/chroma/styles"
36 | "github.com/gin-gonic/gin"
37 | "github.com/go-git/go-git/v5"
38 | "github.com/go-git/go-git/v5/plumbing"
39 | "github.com/go-git/go-git/v5/plumbing/filemode"
40 | "github.com/go-git/go-git/v5/plumbing/object"
41 | "github.com/go-git/go-git/v5/plumbing/storer"
42 | "github.com/yuin/goldmark"
43 | highlighting "github.com/yuin/goldmark-highlighting"
44 |
45 | "embed"
46 |
47 | githttp "github.com/honza/smithy/pkg/go-git-http"
48 | )
49 |
50 | //go:embed templates
51 | var templatefiles embed.FS
52 |
53 | //go:embed static
54 | var staticfiles embed.FS
55 |
56 | const PAGE_SIZE int = 100
57 |
58 | type RepositoryWithName struct {
59 | Name string
60 | Repository *git.Repository
61 | Meta RepoConfig
62 | }
63 |
64 | type Commit struct {
65 | Commit *object.Commit
66 | Subject string
67 | ShortHash string
68 | }
69 |
70 | func (c *Commit) FormattedDate() string {
71 | return c.Commit.Author.When.Format("2006-01-02")
72 | // return c.Commit.Author.When.Format(time.RFC822)
73 | }
74 |
75 | type TreeEntry struct {
76 | Name string
77 | Mode filemode.FileMode
78 | Hash plumbing.Hash
79 | }
80 |
81 | func (te *TreeEntry) FileMode() string {
82 | osFile, err := te.Mode.ToOSFileMode()
83 | if err != nil {
84 | return ""
85 | }
86 |
87 | if osFile.IsDir() {
88 | return "d---------"
89 | }
90 |
91 | return osFile.String()
92 | }
93 |
94 | func ConvertTreeEntries(entries []object.TreeEntry) []TreeEntry {
95 | var results []TreeEntry
96 |
97 | for _, entry := range entries {
98 | e := TreeEntry{
99 | Name: entry.Name,
100 | Mode: entry.Mode,
101 | Hash: entry.Hash,
102 | }
103 | results = append(results, e)
104 | }
105 |
106 | return results
107 | }
108 |
109 | type RepositoryByName []RepositoryWithName
110 |
111 | func (r RepositoryByName) Len() int { return len(r) }
112 | func (r RepositoryByName) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
113 | func (r RepositoryByName) Less(i, j int) bool {
114 | res := strings.Compare(r[i].Name, r[j].Name)
115 | return res < 0
116 | }
117 |
118 | type ReferenceByName []*plumbing.Reference
119 |
120 | func (r ReferenceByName) Len() int { return len(r) }
121 | func (r ReferenceByName) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
122 | func (r ReferenceByName) Less(i, j int) bool {
123 | res := strings.Compare(r[i].Name().String(), r[j].Name().String())
124 | return res < 0
125 | }
126 |
127 | func PathExists(path string) (bool, error) {
128 | _, err := os.Stat(path)
129 | if err == nil {
130 | return true, nil
131 | }
132 | if os.IsNotExist(err) {
133 | return false, nil
134 | }
135 | return false, err
136 | }
137 |
138 | func DefaultParam(ctx *gin.Context, key, def string) string {
139 | p := ctx.Param(key)
140 |
141 | if p != "" {
142 | return p
143 | }
144 |
145 | return def
146 | }
147 |
148 | func GetReadmeFromCommit(commit *object.Commit) (*object.File, error) {
149 | options := []string{
150 | "README.md",
151 | "README",
152 | "README.markdown",
153 | "readme.md",
154 | "readme.markdown",
155 | "readme",
156 | }
157 |
158 | for _, opt := range options {
159 | f, err := commit.File(opt)
160 |
161 | if err == nil {
162 | return f, nil
163 | }
164 |
165 | }
166 |
167 | return nil, errors.New("no valid readme")
168 | }
169 |
170 | func FormatMarkdown(input string) string {
171 | var buf bytes.Buffer
172 | markdown := goldmark.New(
173 | goldmark.WithExtensions(
174 | highlighting.NewHighlighting(
175 | highlighting.WithFormatOptions(
176 | html.WithClasses(true),
177 | ),
178 | ),
179 | ),
180 | )
181 |
182 | if err := markdown.Convert([]byte(input), &buf); err != nil {
183 | panic(err)
184 | }
185 |
186 | return buf.String()
187 |
188 | }
189 |
190 | func RenderSyntaxHighlighting(file *object.File) (string, error) {
191 | contents, err := file.Contents()
192 | if err != nil {
193 | return "", err
194 | }
195 | lexer := lexers.Match(file.Name)
196 | if lexer == nil {
197 | // If the lexer is nil, we weren't able to find one based on the file
198 | // extension. We can render it as plain text.
199 | return fmt.Sprintf("