From 97d27375c673fb5f3c8986aac09f0b41e4f1d0cd Mon Sep 17 00:00:00 2001 From: Gabriel Csapo Date: Fri, 10 Nov 2017 10:39:51 -0800 Subject: [PATCH 01/26] updates docs (#26) * updates docs - adds extensive docs to Git, Util and Service - adds named function to events to trace errors more easily * fixes req to be typed http.IncomingMessage --- CHANGELOG.md | 5 + docs/code/Git.html | 2357 +++++++++++++++++++++++- docs/code/HttpDuplex.html | 100 +- docs/code/Service.html | 385 +++- docs/code/git.js.html | 230 +-- docs/code/global.html | 954 ---------- docs/code/http-duplex.js.html | 104 +- docs/code/index.html | 2 +- docs/code/module-lib_util.html | 453 ++++- docs/code/service.js.html | 77 +- docs/code/util.js.html | 75 +- lib/git.js | 228 +-- lib/http-duplex.js | 102 +- lib/service.js | 75 +- lib/util.js | 73 +- test/{httpduplex.js => http-duplex.js} | 18 +- 16 files changed, 3666 insertions(+), 1572 deletions(-) delete mode 100644 docs/code/global.html rename test/{httpduplex.js => http-duplex.js} (93%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 060b988..d3ef6d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# Unreleased + +- adds extensive docs to Git, Util and Service +- adds named function to events to trace errors more easily + # 0.3.3 (11/05/2017) - Removes dependency on http-duplex package replacing w/ internal replacement lib diff --git a/docs/code/Git.html b/docs/code/Git.html index b13e5a0..68653ba 100644 --- a/docs/code/Git.html +++ b/docs/code/Git.html @@ -22,7 +22,7 @@
@@ -53,7 +53,7 @@

-

new Git(repoDir, options) → {Git}

+

new Git(repoDir, options)

@@ -65,7 +65,7 @@

new GitSource:
@@ -105,49 +105,7 @@

new Git -

events

repos.on('push', function (push) { ... }

Emitted when somebody does a git push to the repo.

-

Exactly one listener must call push.accept() or push.reject(). If there are - no listeners, push.accept() is called automatically.

-

push is an http duplex object (see below) with these extra properties: -push.repo -push.commit -push.branch

-

repos.on('tag', function (tag) { ... }

Emitted when somebody does a git push --tags to the repo.

-

Exactly one listener must call tag.accept() or tag.reject(). If there are - no listeners, tag.accept() is called automatically.

-

tag is an http duplex object (see below) with these extra properties: -tag.repo -tag.commit -tag.version

-

repos.on('fetch', function (fetch) { ... }

Emitted when somebody does a git fetch to the repo (which happens whenever you - do a git pull or a git clone).

-

Exactly one listener must call fetch.accept() or fetch.reject(). If there are - no listeners, fetch.accept() is called automatically.

-

fetch is an http duplex objects (see below) with these extra properties: -fetch.repo -fetch.commit

-

repos.on('info', function (info) { ... }

Emitted when the repo is queried for info before doing other commands.

-

Exactly one listener must call info.accept() or info.reject(). If there are - no listeners, info.accept() is called automatically.

-

info is an http duplex object (see below) with these extra properties: -info.repo

-

repos.on('head', function (head) { ... }

Emitted when the repo is queried for HEAD before doing other commands.

-

Exactly one listener must call head.accept() or head.reject(). If there are - no listeners, head.accept() is called automatically.

-

head is an http duplex object (see below) with these extra properties: -head.repo

-

push.on('response', function(response, done) { ... })

Emitted when node-git-server creates a response stream that will be sent to the git client on the other end.

-

This should really only be used if you want to send verbose or error messages to the remote git client.

-

response is a writable stream that can accept buffers containing git packfile sidechannel transfer protocol encoded strings. done is a callback that must be called when you want to end the response.

-

If you create a response listener then you must either call the done function or execute the following end sequence when you want to end the response:

-
   response.queue(new Buffer('0000'))
-   response.queue(null)

If you never use the response event then the above data will be sent by default. Binding a listener to the response event will prevent the end sequence those from being sent, so you must send them yourself after sending any other messages.

-

http duplex objects

The arguments to each of the events 'push', 'fetch', 'info', and 'head' are http duplex that act as both http - server request and http server response objects so you can pipe to and from them.

-

For every event if there are no listeners dup.accept() will be called - automatically.

-

dup.accept()

Accept the pending request.

-

dup.reject()

Reject the pending request.

+

Handles invoking the git-*-pack binaries

@@ -360,8 +318,7 @@
Properties
-

If opts.checkout is true, create and expected checked-out repos instead of - bare repos

+

If opts.checkout is true, create and expected checked-out repos instead of bare repos

@@ -388,38 +345,57 @@
Properties
-
Returns:
- + + + + +

Extends

-
-
- Type -
-
-Git -
-
+ + + - + + +

Methods

- + + + + + +

(static) close()

+ + + + + + +
+
Source:
+
+ @@ -428,35 +404,68 @@
Returns:
- - + + + + + + + -
+ -
-

- Git -

- + -
-
-
+
+ + + + + +
+

closes the server instance

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + -

new Git()

+

(static) create(repo, callbackopt)

@@ -468,7 +477,7 @@

new GitSource:
@@ -507,6 +516,12 @@

new Git +

Create a new bare repository repoName in the instance repository directory.

+ + + + @@ -515,32 +530,138 @@

new GitParameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDescription
repo + + +String + + + + + +

the name of the repo

callback + + +function + + + + + + <optional>
+ + + + + +

Optionally get a callback cb(err) to be notified when the repository was created.

+ + + + + + + + + + + + + + + + + + + +

(static) exists(repo, callbackopt)

+ + + + + + +
+
Source:
+
+ - + + + @@ -551,6 +672,2092 @@

new Git + + + + + +
+

Find out whether repoName exists in the callback cb(exists).

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDescription
repo + + +String + + + + + + + + + +

name of the repo

callback + + +function + + + + + + <optional>
+ + + + + +

function to be called when finished

+ + + + + + + + + + + + + + + + + + + + + +

(static) handle(req, res)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Handle incoming HTTP requests with a connect-style middleware

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
req + + +Object + + + +

http request object

res + + +Object + + + +

http response object

+ + + + + + + + + + + + + + + + + + + + + +

(static) list(callback)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get a list of all the repositories

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
callback + + +function + + + +

function to be called when repositories have been found function(error, repos)

+ + + + + + + + + + + + + + + + + + + + + +

(static) listen(port, callback)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

starts a git server on the given port

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
port + + +Number + + + +

the port to start the server on

callback + + +function + + + +

the function to call when server is started or error has occured

+ + + + + + + + + + + + + + + + + + + + + +

(static) mkdir(dir, callbackopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Create a subdirectory dir in the repo dir with a callback cb(err).

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDescription
dir + + +String + + + + + + + + + +

directory name

callback + + +function + + + + + + <optional>
+ + + + + +

callback to be called when finished

+ + + + + + + + + + + + + + + + + + + + + + +

Events

+ + + + + + +

fetch

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
Properties:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
fetch + + +HttpDuplex + + + +

an http duplex object (see below) with these extra properties:

+
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
repo + + +String + + + +

the string that defines the repo

commit + + +String + + + +

the string that defines the commit sha

+ +
+ + + + + + + + + + +
Type:
+
    +
  • + +Object + + +
  • +
+ + + + + +
Example
+ +
repos.on('fetch', function (fetch) { ... }
+
+    Emitted when somebody does a `git fetch` to the repo (which happens whenever you
+    do a `git pull` or a `git clone`).
+
+    Exactly one listener must call `fetch.accept()` or `fetch.reject()`. If there are
+    no listeners, `fetch.accept()` is called automatically.
+ + + + + + + + + + + + + + + + + + + + + + + +

head

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
Properties:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
head + + +HttpDuplex + + + +

an http duplex object (see below) with these extra properties:

+
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
repo + + +String + + + +

the string that defines the repo

+ +
+ + + + + + + + + + +
Type:
+
    +
  • + +Object + + +
  • +
+ + + + + +
Example
+ +
repos.on('head', function (head) { ... }
+
+    Emitted when the repo is queried for HEAD before doing other commands.
+
+    Exactly one listener must call `head.accept()` or `head.reject()`. If there are
+    no listeners, `head.accept()` is called automatically.
+ + + + + + + + + + + + + + + + + + + + + + + +

info

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
Properties:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
info + + +HttpDuplex + + + +

an http duplex object (see below) with these extra properties:

+
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
repo + + +String + + + +

the string that defines the repo

+ +
+ + + + + + + + + + +
Type:
+
    +
  • + +Object + + +
  • +
+ + + + + +
Example
+ +
repos.on('info', function (info) { ... }
+
+    Emitted when the repo is queried for info before doing other commands.
+
+    Exactly one listener must call `info.accept()` or `info.reject()`. If there are
+    no listeners, `info.accept()` is called automatically.
+ + + + + + + + + + + + + + + + + + + + + + + +

info

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
Properties:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
info + + +HttpDuplex + + + +

an http duplex object (see below) with these extra properties:

+
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
repo + + +String + + + +

the string that defines the repo

+ +
+ + + + + + + + + + +
Type:
+
    +
  • + +Object + + +
  • +
+ + + + + +
Example
+ +
repos.on('info', function (info) { ... }
+
+    Emitted when the repo is queried for info before doing other commands.
+
+    Exactly one listener must call `info.accept()` or `info.reject()`. If there are
+    no listeners, `info.accept()` is called automatically.
+ + + + + + + + + + + + + + + + + + + + + + + +

push

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
Properties:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
push + + +HttpDuplex + + + +

is a http duplex object (see below) with these extra properties

+
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
repo + + +String + + + +

the string that defines the repo

commit + + +String + + + +

the string that defines the commit sha

branch + + +String + + + +

the string that defines the branch

+ +
+ + + + + + + + + + +
Type:
+
    +
  • + +Object + + +
  • +
+ + + + + +
Example
+ +
repos.on('push', function (push) { ... }
+
+    Emitted when somebody does a `git push` to the repo.
+
+    Exactly one listener must call `push.accept()` or `push.reject()`. If there are
+    no listeners, `push.accept()` is called automatically.
+ + + + + + + + + + + + + + + + + + + + + + + +

tag

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
Properties:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
tag + + +HttpDuplex + + + +

an http duplex object (see below) with these extra properties:

+
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
repo + + +String + + + +

the string that defines the repo

commit + + +String + + + +

the string that defines the commit sha

version + + +String + + + +

the string that defines the repo

+ +
+ + + + + + + + + + +
Type:
+
    +
  • + +Object + + +
  • +
+ + + + + +
Example
+ +
repos.on('tag', function (tag) { ... }
+
+    Emitted when somebody does a `git push --tags` to the repo.
+    Exactly one listener must call `tag.accept()` or `tag.reject()`. If there are
+    No listeners, `tag.accept()` is called automatically.
+ + + + + + + + + + + + + + + + + + + + diff --git a/docs/code/HttpDuplex.html b/docs/code/HttpDuplex.html index 76c7f6f..3bc0df1 100644 --- a/docs/code/HttpDuplex.html +++ b/docs/code/HttpDuplex.html @@ -22,7 +22,7 @@
@@ -266,7 +266,7 @@

Members

-

(readonly) headers :object

+

(readonly) headers :Object

@@ -333,7 +333,7 @@
Type:
  • -object +Object
  • @@ -426,7 +426,7 @@
    Type:
    -

    (readonly) method :string

    +

    (readonly) method :String

    @@ -495,7 +495,7 @@
    Type:
    • -string +String
    • @@ -513,7 +513,7 @@
      Example
      -

      (readonly) readable :boolean

      +

      (readonly) readable :Boolean

      @@ -573,7 +573,7 @@
      Type:
      • -boolean +Boolean
      • @@ -646,8 +646,8 @@

        req -

        A IncomingMessage created by http.Server or http.ClientRequest usually passed as the -first parameter to the 'request' and 'response' events. Implements Readable Stream interface +

        A IncomingMessage created by http.Server or http.ClientRequest usually passed as the +first parameter to the 'request' and 'response' events. Implements Readable Stream interface but may not be a decendant thereof.

@@ -833,7 +833,7 @@
Type:
-

statusCode :number

+

statusCode :Number

@@ -905,7 +905,7 @@
Type:
  • -number +Number
  • @@ -923,7 +923,7 @@
    Example
    -

    statusMessage :string

    +

    statusMessage :String

    @@ -996,7 +996,7 @@
    Type:
    • -string +String
    • @@ -1014,7 +1014,7 @@
      Example
      -

      (readonly) trailers :object

      +

      (readonly) trailers :Object

      @@ -1078,9 +1078,9 @@

      (readonly) tr
      -

      Request/response trailer headers. Just like headers except these are only written +

      Request/response trailer headers. Just like headers except these are only written after the initial response to the client. -This object is only populated at the 'end' event and only work if a 'transfer-encoding: chunked' +This object is only populated at the 'end' event and only work if a 'transfer-encoding: chunked' header is sent in the initial response.

      @@ -1090,7 +1090,7 @@

      Type:
      • -object +Object
      • @@ -1103,7 +1103,7 @@
        Type:
        -

        (readonly) upgrade :boolean

        +

        (readonly) upgrade :Boolean

        @@ -1170,7 +1170,7 @@
        Type:
        • -boolean +Boolean
        • @@ -1183,7 +1183,7 @@
          Type:
          -

          (readonly) url :string

          +

          (readonly) url :String

          @@ -1243,7 +1243,7 @@
          Type:
          • -string +String
          • @@ -1267,7 +1267,7 @@
            Examples
            -

            (readonly) httpVersion :string

            +

            (readonly) httpVersion :String

            @@ -1334,7 +1334,7 @@
            Type:
            • -string +String
            • @@ -1347,7 +1347,7 @@
              Type:
              -

              (readonly) httpVersionMajor :number

              +

              (readonly) httpVersionMajor :Number

              @@ -1414,7 +1414,7 @@
              Type:
              • -number +Number
              • @@ -1427,7 +1427,7 @@
                Type:
                -

                (readonly) httpVersionMinor :string

                +

                (readonly) httpVersionMinor :String

                @@ -1494,7 +1494,7 @@
                Type:
                • -string +String
                • @@ -1626,7 +1626,7 @@
                  Parameters:
                  -object +Object @@ -1980,7 +1980,7 @@
                  Parameters:
                  -string +String @@ -2040,7 +2040,7 @@
                  Parameters:
                  -

                  getHeader(name) → {string}

                  +

                  getHeader(name) → {String}

                  @@ -2148,7 +2148,7 @@
                  Parameters:
                  -string +String @@ -2189,7 +2189,7 @@
                  Returns:
                  -string +String
                  @@ -2262,7 +2262,7 @@

                  pause -

                  Switch readable stream out of flowing mode and stop emitting 'data' events. +

                  Switch readable stream out of flowing mode and stop emitting 'data' events. Any new data that becomes available during this time will stay buffered until resume is called.

                  @@ -2403,7 +2403,7 @@
                  Parameters:
                  -string +String @@ -2640,7 +2640,7 @@
                  Parameters:
                  -string +String @@ -2749,7 +2749,7 @@

                  setEncodin

                  Example
                  -
                  request.setEncoding('utf8'); 
                  +
                  request.setEncoding('utf8');
                  @@ -2785,7 +2785,7 @@
                  Parameters:
                  -string +String @@ -2883,7 +2883,7 @@

                  setHeader -

                  Set a single header. If the header already exists, it will be replaced. +

                  Set a single header. If the header already exists, it will be replaced. It's possible to use an array of strings in place of value to send multiple headers with the same name.

                  @@ -2939,7 +2939,7 @@
                  Parameters:
                  -string +String @@ -3095,7 +3095,7 @@

                  uncorkwrite(chunk, encodingopt, callbackopt) → {boolean}

                  +

                  write(chunk, encodingopt, callbackopt) → {Boolean}

                  @@ -3159,7 +3159,7 @@

                  write

                  Note: If write() is called either before writeHead() or writeHead() just hasn't been called, it will switch * modes and flush the implicit headers that may be waiting before parts of this chunk are sent.

                  Node will buffer up to the first chunk of the body. Any additional calls to write() may be buffered as well for packet efficiency purposes.

                  -Returns true if the entire data was flushed successfully to the kernel buffer. Returns false if all or part of +Returns true if the entire data was flushed successfully to the kernel buffer. Returns false if all or part of the data was buffered in memory.

                  @@ -3246,7 +3246,7 @@
                  Parameters:
                  -string +String @@ -3345,7 +3345,7 @@
                  Returns:
                  -boolean +Boolean
                  @@ -3526,7 +3526,7 @@
                  Example
                  var content = 'Under Construction...';
                   response.writeHead(200, {
                       'Content-Length': Buffer.byteLength(content),
                  -    'Content-Type': 'text/plain' 
                  +    'Content-Type': 'text/plain'
                   });
                   response.end(content);
                  @@ -3566,7 +3566,7 @@
                  Parameters:
                  -number +Number @@ -3597,7 +3597,7 @@
                  Parameters:
                  -string +String @@ -3630,7 +3630,7 @@
                  Parameters:
                  -object +Object @@ -3738,7 +3738,7 @@

                  writeHeade
                  -

                  Warning: This has been deprecated in node, don't use it. Any apis that require this funtion should be +

                  Warning: This has been deprecated in node, don't use it. Any apis that require this funtion should be updated to use writeHead insted.

                  @@ -3995,7 +3995,7 @@

                  Parameters:
                  -

                  The chunk is either a buffer or string when the stream isn't operating +

                  The chunk is either a buffer or string when the stream isn't operating in object mode. When the stream is in object mode, the chunk can be any JavaScript value other than null.

                  @@ -4082,7 +4082,7 @@

                  drain

                  -

                  If a call to response.write(chunk) returns false, the drain event will be emitted once it is appropriate to +

                  If a call to response.write(chunk) returns false, the drain event will be emitted once it is appropriate to resume writing data to the stream.

                  diff --git a/docs/code/Service.html b/docs/code/Service.html index 7ed4ed7..66552ea 100644 --- a/docs/code/Service.html +++ b/docs/code/Service.html @@ -22,7 +22,7 @@
                  @@ -53,7 +53,7 @@

                  -

                  new Service()

                  +

                  new Service(opts, req, res)

                  @@ -65,7 +65,7 @@

                  new ServiceSource:
                  @@ -104,6 +104,9 @@

                  new Service +

                  Handles invoking the git-*-pack binaries

                  +

                  @@ -115,6 +118,102 @@

                  new ServiceParameters:

                  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                  NameTypeDescription
                  opts + + +Object + + + +

                  options to bootstrap the service object

                  req + + +http.IncomingMessage + + + +

                  http request object

                  res + + +http.ServerResponse + + + +

                  http response

                  + + + @@ -242,7 +341,7 @@
                  Type:
                  -

                  (readonly) method :string

                  +

                  (readonly) method :String

                  @@ -316,7 +415,7 @@
                  Type:
                  • -string +String
                  • @@ -334,7 +433,7 @@
                    Example
                    -

                    (readonly) readable :boolean

                    +

                    (readonly) readable :Boolean

                    @@ -399,7 +498,7 @@
                    Type:
                    • -boolean +Boolean
                    • @@ -477,8 +576,8 @@

                      req -

                      A IncomingMessage created by http.Server or http.ClientRequest usually passed as the -first parameter to the 'request' and 'response' events. Implements Readable Stream interface +

                      A IncomingMessage created by http.Server or http.ClientRequest usually passed as the +first parameter to the 'request' and 'response' events. Implements Readable Stream interface but may not be a decendant thereof.

                      @@ -674,7 +773,7 @@
                      Type:
                      -

                      statusCode :number

                      +

                      statusCode :Number

                      @@ -751,7 +850,7 @@
                      Type:
                      • -number +Number
                      • @@ -769,7 +868,7 @@
                        Example
                        -

                        statusMessage :string

                        +

                        statusMessage :String

                        @@ -847,7 +946,7 @@
                        Type:
                        • -string +String
                        • @@ -865,7 +964,7 @@
                          Example
                          -

                          (readonly) trailers :object

                          +

                          (readonly) trailers :Object

                          @@ -934,9 +1033,9 @@

                          (readonly) tr
                          -

                          Request/response trailer headers. Just like headers except these are only written +

                          Request/response trailer headers. Just like headers except these are only written after the initial response to the client. -This object is only populated at the 'end' event and only work if a 'transfer-encoding: chunked' +This object is only populated at the 'end' event and only work if a 'transfer-encoding: chunked' header is sent in the initial response.

                          @@ -946,7 +1045,7 @@

                          Type:
                          • -object +Object
                          • @@ -959,7 +1058,7 @@
                            Type:
                            -

                            (readonly) upgrade :boolean

                            +

                            (readonly) upgrade :Boolean

                            @@ -1031,7 +1130,7 @@
                            Type:
                            • -boolean +Boolean
                            • @@ -1044,7 +1143,7 @@
                              Type:
                              -

                              (readonly) url :string

                              +

                              (readonly) url :String

                              @@ -1109,7 +1208,7 @@
                              Type:
                              • -string +String
                              • @@ -1135,6 +1234,250 @@
                                Examples
                                +

                                Methods

                                + + + + + + +

                                (static) accept()

                                + + + + + + +
                                + + +
                                Source:
                                +
                                + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                                + + + + + +
                                +

                                accepts request to access resource

                                +
                                + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

                                (static) reject(code, msg)

                                + + + + + + +
                                + + +
                                Source:
                                +
                                + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                                + + + + + +
                                +

                                reject request in flight

                                +
                                + + + + + + + + + + + +
                                Parameters:
                                + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                                NameTypeDescription
                                code + + +Number + + + +

                                http response code

                                msg + + +String + + + +

                                message that should be displayed on teh client

                                + + + + + + + + + + + + + + + + + + diff --git a/docs/code/git.js.html b/docs/code/git.js.html index c178c28..c5abe24 100644 --- a/docs/code/git.js.html +++ b/docs/code/git.js.html @@ -22,7 +22,7 @@
                                @@ -52,14 +52,109 @@

                                git.js

                                const services = ['upload-pack', 'receive-pack']; /** - * @class Git - */ + * @event Git#push + * @type {Object} + * @property {HttpDuplex} push - is a http duplex object (see below) with these extra properties + * @property {String} push.repo - the string that defines the repo + * @property {String} push.commit - the string that defines the commit sha + * @property {String} push.branch - the string that defines the branch + * @example + repos.on('push', function (push) { ... } + + Emitted when somebody does a `git push` to the repo. + + Exactly one listener must call `push.accept()` or `push.reject()`. If there are + no listeners, `push.accept()` is called automatically. + * +**/ + +/** + * @event Git#tag + * @type {Object} + * @property {HttpDuplex} tag - an http duplex object (see below) with these extra properties: + * @property {String} tag.repo - the string that defines the repo + * @property {String} tag.commit - the string that defines the commit sha + * @property {String} tag.version - the string that defines the repo + * @example + repos.on('tag', function (tag) { ... } + + Emitted when somebody does a `git push --tags` to the repo. + Exactly one listener must call `tag.accept()` or `tag.reject()`. If there are + No listeners, `tag.accept()` is called automatically. + * +**/ + +/** + * @event Git#fetch + * @type {Object} + * @property {HttpDuplex} fetch - an http duplex object (see below) with these extra properties: + * @property {String} fetch.repo - the string that defines the repo + * @property {String} fetch.commit - the string that defines the commit sha + * @example + repos.on('fetch', function (fetch) { ... } + + Emitted when somebody does a `git fetch` to the repo (which happens whenever you + do a `git pull` or a `git clone`). + + Exactly one listener must call `fetch.accept()` or `fetch.reject()`. If there are + no listeners, `fetch.accept()` is called automatically. + * +*/ + +/** + * @event Git#info + * @type {Object} + * @property {HttpDuplex} info - an http duplex object (see below) with these extra properties: + * @property {String} info.repo - the string that defines the repo + * @example + repos.on('info', function (info) { ... } + + Emitted when the repo is queried for info before doing other commands. + + Exactly one listener must call `info.accept()` or `info.reject()`. If there are + no listeners, `info.accept()` is called automatically. + * +*/ + +/** + * @event Git#info + * @type {Object} + * @property {HttpDuplex} info - an http duplex object (see below) with these extra properties: + * @property {String} info.repo - the string that defines the repo + * @example + repos.on('info', function (info) { ... } + + Emitted when the repo is queried for info before doing other commands. + + Exactly one listener must call `info.accept()` or `info.reject()`. If there are + no listeners, `info.accept()` is called automatically. + * +*/ + +/** + * @event Git#head + * @type {Object} + * @property {HttpDuplex} head - an http duplex object (see below) with these extra properties: + * @property {String} head.repo - the string that defines the repo + * @example + repos.on('head', function (head) { ... } + + Emitted when the repo is queried for HEAD before doing other commands. + + Exactly one listener must call `head.accept()` or `head.reject()`. If there are + no listeners, `head.accept()` is called automatically. + * +*/ + class Git extends EventEmitter { /** - * returns an instance of Git + * + * Handles invoking the git-*-pack binaries + * @class Git + * @extends EventEmitter * @param {(String|Function)} repoDir - Create a new repository collection from the directory `repoDir`. `repoDir` should be entirely empty except for git repo directories. If `repoDir` is a function, `repoDir(repo)` will be used to dynamically resolve project directories. The return value of `repoDir(repo)` should be a string path specifying where to put the string `repo`. Make sure to return the same value for `repo` every time since `repoDir(repo)` will be called multiple times. * @param {Object} options - options that can be applied on the new instance being created - * @param {Boolean=} options.autoCreate - By default, repository targets will be created if they don't exist. You can + * @param {Boolean=} options.autoCreate - By default, repository targets will be created if they don't exist. You can disable that behavior with `options.autoCreate = true` * @param {Function} options.authenticate - a function that has the following arguments (repo, username, password, next) and will be called when a request comes through if set * @@ -77,106 +172,8 @@

                                git.js

                                return reject("sorry you don't have access to this content"); }); } - * @param {Boolean=} options.checkout - If `opts.checkout` is true, create and expected checked-out repos instead of - bare repos - * @return {Git} - * @description - # events - - ## repos.on('push', function (push) { ... } - - Emitted when somebody does a `git push` to the repo. - - Exactly one listener must call `push.accept()` or `push.reject()`. If there are - no listeners, `push.accept()` is called automatically. - - `push` is an http duplex object (see below) with these extra properties: - - * push.repo - * push.commit - * push.branch - - ## repos.on('tag', function (tag) { ... } - - Emitted when somebody does a `git push --tags` to the repo. - - Exactly one listener must call `tag.accept()` or `tag.reject()`. If there are - no listeners, `tag.accept()` is called automatically. - - `tag` is an http duplex object (see below) with these extra properties: - - * tag.repo - * tag.commit - * tag.version - - ## repos.on('fetch', function (fetch) { ... } - - Emitted when somebody does a `git fetch` to the repo (which happens whenever you - do a `git pull` or a `git clone`). - - Exactly one listener must call `fetch.accept()` or `fetch.reject()`. If there are - no listeners, `fetch.accept()` is called automatically. - - `fetch` is an http duplex objects (see below) with these extra properties: - - * fetch.repo - * fetch.commit - - ## repos.on('info', function (info) { ... } - - Emitted when the repo is queried for info before doing other commands. - - Exactly one listener must call `info.accept()` or `info.reject()`. If there are - no listeners, `info.accept()` is called automatically. - - `info` is an http duplex object (see below) with these extra properties: - - * info.repo - - ## repos.on('head', function (head) { ... } - - Emitted when the repo is queried for HEAD before doing other commands. - - Exactly one listener must call `head.accept()` or `head.reject()`. If there are - no listeners, `head.accept()` is called automatically. - - `head` is an http duplex object (see below) with these extra properties: - - * head.repo - - ## push.on('response', function(response, done) { ... }) - - Emitted when node-git-server creates a response stream that will be sent to the git client on the other end. - - This should really only be used if you want to send verbose or error messages to the remote git client. - - `response` is a writable stream that can accept buffers containing git packfile sidechannel transfer protocol encoded strings. `done` is a callback that must be called when you want to end the response. - - If you create a response listener then you must either call the `done` function or execute the following end sequence when you want to end the response: - - ```js - response.queue(new Buffer('0000')) - response.queue(null) - ``` - - If you never use the response event then the above data will be sent by default. Binding a listener to the response event will prevent the end sequence those from being sent, so you must send them yourself after sending any other messages. - - # http duplex objects - - The arguments to each of the events `'push'`, `'fetch'`, `'info'`, and `'head'` are [http duplex](http://github.com/substack/http-duplex) that act as both http - server request and http server response objects so you can pipe to and from them. - - For every event if there are no listeners `dup.accept()` will be called - automatically. - - ## dup.accept() - - Accept the pending request. - - ## dup.reject() - - Reject the pending request. - */ + * @param {Boolean=} options.checkout - If `opts.checkout` is true, create and expected checked-out repos instead of bare repos + */ constructor(repoDir, options={}) { super(); @@ -195,6 +192,7 @@

                                git.js

                                /** * Get a list of all the repositories * @method list + * @memberof Git * @param {Function} callback function to be called when repositories have been found `function(error, repos)` */ list(callback) { @@ -210,6 +208,7 @@

                                git.js

                                /** * Find out whether `repoName` exists in the callback `cb(exists)`. * @method exists + * @memberof Git * @param {String} repo - name of the repo * @param {Function=} callback - function to be called when finished */ @@ -219,6 +218,7 @@

                                git.js

                                /** * Create a subdirectory `dir` in the repo dir with a callback `cb(err)`. * @method mkdir + * @memberof Git * @param {String} dir - directory name * @param {Function=} callback - callback to be called when finished */ @@ -236,6 +236,7 @@

                                git.js

                                /** * Create a new bare repository `repoName` in the instance repository directory. * @method create + * @memberof Git * @param {String} repo - the name of the repo * @param {Function=} callback - Optionally get a callback `cb(err)` to be notified when the repository was created. */ @@ -277,6 +278,7 @@

                                git.js

                                /** * Handle incoming HTTP requests with a connect-style middleware * @method handle + * @memberof Git * @param {Object} req - http request object * @param {Object} res - http response object */ @@ -315,11 +317,7 @@

                                git.js

                                res.end(error); return; } else { - return infoResponse({ - repos: self, - repo: repo, - service: service, - }, req, res); + return infoResponse(self, repo, service, req, res); } }; @@ -448,6 +446,13 @@

                                git.js

                                if (x === false) next(ix + 1); })(0); } + /** + * starts a git server on the given port + * @method listen + * @memberof Git + * @param {Number} port - the port to start the server on + * @param {Function} callback - the function to call when server is started or error has occured + */ listen(port, callback) { var self = this; this.server = http.createServer(function(req, res) { @@ -455,6 +460,11 @@

                                git.js

                                }); this.server.listen(port, callback); } + /** + * closes the server instance + * @method close + * @memberof Git + */ close() { this.server.close(); } diff --git a/docs/code/global.html b/docs/code/global.html deleted file mode 100644 index 3610f86..0000000 --- a/docs/code/global.html +++ /dev/null @@ -1,954 +0,0 @@ - - - - - Global - Documentation - - - - - - - - - - - - - - - - -
                                - -

                                Global

                                - - - - - - - -
                                - -
                                - -

                                - -

                                - - -
                                - -
                                -
                                - - - -
                                - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
                                - - - - - - - - -
                                - - - - - - - - - - - - - - -

                                Methods

                                - - - - - - -

                                create(repo, callbackopt)

                                - - - - - - -
                                - - -
                                Source:
                                -
                                - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
                                - - - - - -
                                -

                                Create a new bare repository repoName in the instance repository directory.

                                -
                                - - - - - - - - - - - -
                                Parameters:
                                - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
                                NameTypeAttributesDescription
                                repo - - -String - - - - - - - - - -

                                the name of the repo

                                callback - - -function - - - - - - <optional>
                                - - - - - -

                                Optionally get a callback cb(err) to be notified when the repository was created.

                                - - - - - - - - - - - - - - - - - - - - - -

                                exists(repo, callbackopt)

                                - - - - - - -
                                - - -
                                Source:
                                -
                                - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
                                - - - - - -
                                -

                                Find out whether repoName exists in the callback cb(exists).

                                -
                                - - - - - - - - - - - -
                                Parameters:
                                - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
                                NameTypeAttributesDescription
                                repo - - -String - - - - - - - - - -

                                name of the repo

                                callback - - -function - - - - - - <optional>
                                - - - - - -

                                function to be called when finished

                                - - - - - - - - - - - - - - - - - - - - - -

                                handle(req, res)

                                - - - - - - -
                                - - -
                                Source:
                                -
                                - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
                                - - - - - -
                                -

                                Handle incoming HTTP requests with a connect-style middleware

                                -
                                - - - - - - - - - - - -
                                Parameters:
                                - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
                                NameTypeDescription
                                req - - -Object - - - -

                                http request object

                                res - - -Object - - - -

                                http response object

                                - - - - - - - - - - - - - - - - - - - - - -

                                list(callback)

                                - - - - - - -
                                - - -
                                Source:
                                -
                                - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
                                - - - - - -
                                -

                                Get a list of all the repositories

                                -
                                - - - - - - - - - - - -
                                Parameters:
                                - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
                                NameTypeDescription
                                callback - - -function - - - -

                                function to be called when repositories have been found function(error, repos)

                                - - - - - - - - - - - - - - - - - - - - - -

                                mkdir(dir, callbackopt)

                                - - - - - - -
                                - - -
                                Source:
                                -
                                - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
                                - - - - - -
                                -

                                Create a subdirectory dir in the repo dir with a callback cb(err).

                                -
                                - - - - - - - - - - - -
                                Parameters:
                                - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
                                NameTypeAttributesDescription
                                dir - - -String - - - - - - - - - -

                                directory name

                                callback - - -function - - - - - - <optional>
                                - - - - - -

                                callback to be called when finished

                                - - - - - - - - - - - - - - - - - - - - - - -
                                - -
                                - - - - -
                                - -
                                - - - - - - - \ No newline at end of file diff --git a/docs/code/http-duplex.js.html b/docs/code/http-duplex.js.html index 32bba0f..c0e7423 100644 --- a/docs/code/http-duplex.js.html +++ b/docs/code/http-duplex.js.html @@ -22,7 +22,7 @@
                                @@ -40,7 +40,7 @@

                                http-duplex.js

                                const EventEmitter = require('events');
                                 
                                 class HttpDuplex extends EventEmitter {
                                -    /** 
                                +    /**
                                      * Constructs a proxy object over input and output resulting in a unified stream.
                                      * Generally meant to combine request and response streams in the http.request event
                                      * @class HttpDuplex
                                @@ -61,8 +61,8 @@ 

                                http-duplex.js

                                super(); /** - * A IncomingMessage created by http.Server or http.ClientRequest usually passed as the - * first parameter to the 'request' and 'response' events. Implements Readable Stream interface + * A IncomingMessage created by http.Server or http.ClientRequest usually passed as the + * first parameter to the 'request' and 'response' events. Implements Readable Stream interface * but may not be a decendant thereof. * @type {http.IncomingMessage} * @see {@link https://nodejs.org/api/http.html#http_event_request|request} @@ -113,14 +113,14 @@

                                http-duplex.js

                                } /** - * Request/response headers. Key-value pairs of header names and values. Header names are always lower-case. + * Request/response headers. Key-value pairs of header names and values. Header names are always lower-case. * @name headers * @alias HttpDuplex.headers * @memberof HttpDuplex - * @type {object} + * @type {Object} * @readonly * @see {@link https://nodejs.org/api/http.html#http_message_headers|message.headers} - */ + */ get headers() { return this.req.headers; } @@ -130,7 +130,7 @@

                                http-duplex.js

                                * @name httpVersion * @alias HttpDuplex.httpVersion * @memberof HttpDuplex - * @type {string} + * @type {String} * @see {@link https://nodejs.org/api/http.html#http_message_httpversion|message.httpVersion} * @readonly */ @@ -143,7 +143,7 @@

                                http-duplex.js

                                * @name httpVersionMajor * @alias HttpDuplex.httpVersionMajor * @memberof HttpDuplex - * @type {number} + * @type {Number} * @see httpVersion * @readonly */ @@ -156,29 +156,29 @@

                                http-duplex.js

                                * @name httpVersionMinor * @alias HttpDuplex.httpVersionMinor * @memberof HttpDuplex - * @type {string} + * @type {String} * @see httpVersion * @readonly */ get httpVersionMinor() { return this.req.httpVersionMinor; } - + /** * Request method of the incoming request. - * @type {string} + * @type {String} * @see {@link https://nodejs.org/api/http.html#http_event_request|request} * @see {@link https://nodejs.org/api/http.html#http_class_http_serverresponse|http.ServerResponse} * @example 'GET', 'DELETE' * @readonly - */ + */ get method() { return this.req.method; } /** * Is this stream readable. - * @type {boolean} + * @type {Boolean} * @readonly */ get readable() { @@ -187,7 +187,7 @@

                                http-duplex.js

                                /** * net.Socket object associated with the connection. - * @type net.Socket + * @type net.Socket * @see {@link https://nodejs.org/api/net.html#net_class_net_socket|net.Socket} * @readonly */ @@ -196,8 +196,8 @@

                                http-duplex.js

                                } /** - * The HTTP status code. Generally assigned before sending headers for a response to a client. - * @type {number} + * The HTTP status code. Generally assigned before sending headers for a response to a client. + * @type {Number} * @default 200 * @see {@link https://nodejs.org/api/http.html#http_response_statuscode|response.statusCode} * @example request.statusCode = 404; @@ -213,7 +213,7 @@

                                http-duplex.js

                                /** * Controls the status message sent to the client as long as an explicit call to response.writeHead() isn't made * If ignored or the value is undefined, the default message corresponding to the status code will be used. - * @type {string} + * @type {String} * @default undefined * @see {@link https://nodejs.org/api/http.html#http_response_statusmessage|response.statusMessage} * @example request.statusMessage = 'Document Not found'; @@ -227,12 +227,12 @@

                                http-duplex.js

                                } /** - * Request/response trailer headers. Just like {@link headers} except these are only written + * Request/response trailer headers. Just like {@link headers} except these are only written * after the initial response to the client. - * This object is only populated at the 'end' event and only work if a 'transfer-encoding: chunked' + * This object is only populated at the 'end' event and only work if a 'transfer-encoding: chunked' * header is sent in the initial response. * @name HttpDuplex#trailers - * @type {object} + * @type {Object} * @readonly * @see headers * @see addTrailers @@ -245,7 +245,7 @@

                                http-duplex.js

                                /** * Whether or not the client connection has been upgraded - * @type {boolean} + * @type {Boolean} * @see {@link https://nodejs.org/api/http.html#http_event_upgrade_1|upgrade} * @readonly */ @@ -257,9 +257,9 @@

                                http-duplex.js

                                * Request URL string. * @example <caption>A request made as:</caption> * GET /info?check=none HTTP/1.1 - * @example <caption>Will return the string</caption> + * @example <caption>Will return the string</caption> * '/info?check=none' - * @type {string} + * @type {String} * @readonly */ get url() { @@ -319,14 +319,14 @@

                                http-duplex.js

                                * passed as a Buffer. * @event data * @alias HttpDuplex#event:data - * @param {(string|buffer|any)} chunk The chunk is either a buffer or string when the stream isn't operating + * @param {(string|buffer|any)} chunk The chunk is either a buffer or string when the stream isn't operating * in object mode. When the stream is in object mode, the chunk can be any JavaScript value other than null. * @memberof HttpDuplex * @see {@link https://nodejs.org/api/stream.html#stream_event_data|stream.Readable/data} */ /** - * If a call to response.write(chunk) returns false, the drain event will be emitted once it is appropriate to + * If a call to response.write(chunk) returns false, the drain event will be emitted once it is appropriate to * resume writing data to the stream. * @event drain * @alias HttpDuplex#event:drain @@ -361,12 +361,12 @@

                                http-duplex.js

                                /** * Adds trailing headers to the response. - * Trailers will only be emitted if chunked encoding is enabled for the response; otherwise they are discarded. + * Trailers will only be emitted if chunked encoding is enabled for the response; otherwise they are discarded. * @method addTrailers * @name addTrailers * @alias HttpDuplex.addTrailers * @memberof HttpDuplex - * @param {object} headers Trailing headers to add to the response + * @param {Object} headers Trailing headers to add to the response * @see trailers * @see headers * @see {@link https://nodejs.org/api/http.html#http_message_trailers|message.trailers} @@ -396,7 +396,7 @@

                                http-duplex.js

                                * @alias HttpDuplex.end * @memberof HttpDuplex * @param {(string|Buffer)} data optional data to write to the response before closing the connection - * @param {string} encoding Encoding that should be used to write the data + * @param {String} encoding Encoding that should be used to write the data * @param {function} callback Function to be called once the response stream is finished * @see {@link https://nodejs.org/api/http.html#http_response_end_data_encoding_callback|response.end} */ @@ -406,28 +406,28 @@

                                http-duplex.js

                                * @method getHeader * @alias HttpDuplex.getHeader * @memberof HttpDuplex - * @param {string} name Header to get the value of - * @returns {string} + * @param {String} name Header to get the value of + * @returns {String} * @see {@link https://nodejs.org/api/http.html#http_request_getheader_name|getHeader} * @example * let contentType = request.getHeader('Content-Type'); */ /** - * Switch readable stream out of flowing mode and stop emitting 'data' events. + * Switch readable stream out of flowing mode and stop emitting 'data' events. * Any new data that becomes available during this time will stay buffered until resume is called. * @method pause * @alias HttpDuplex.pause * @memberof HttpDuplex * @see {@link https://nodejs.org/api/stream.html#stream_readable_pause|stream.Readable.pause} */ - + /** * Remove a header from the response headers. * @method removeHeader * @alias HttpDuplex.removeHeader * @memberof HttpDuplex - * @param {string} name Header to remove + * @param {String} name Header to remove * @see {@link https://nodejs.org/api/http.html#http_request_removeheader_name|removeHeader} * @example * request.removeHeader('Content-Type'); @@ -441,13 +441,13 @@

                                http-duplex.js

                                * @memberof HttpDuplex * @see {@link https://nodejs.org/api/stream.html#stream_readable_resume|stream.Readable.resume} */ - + /** * Sets the character encoding for data written to the stream. * @method setDefaultEncoding * @alias HttpDuplex.setDefaultEncoding * @memberof HttpDuplex - * @param encoding {string} new character encoding + * @param encoding {String} new character encoding * @see setEncoding * @example request.setDefaultEncoding('utf8'); */ @@ -457,18 +457,18 @@

                                http-duplex.js

                                * @method setEncoding * @alias HttpDuplex.setEncoding * @memberof HttpDuplex - * @param encoding {string} new character encoding + * @param encoding {String} new character encoding * @see setDefaultEncoding - * @example request.setEncoding('utf8'); + * @example request.setEncoding('utf8'); */ /** - * Set a single header. If the header already exists, it will be replaced. + * Set a single header. If the header already exists, it will be replaced. * It's possible to use an array of strings in place of value to send multiple headers with the same name. * @method setHeader * @alias HttpDuplex.setHeader * @memberof HttpDuplex - * @param {string} name Header to set + * @param {String} name Header to set * @param {string|string[]} value Value(s) to assign to header * @see removeHeader * @see {@link https://nodejs.org/api/http.html#http_request_setheader_name_value|setHeader} @@ -477,7 +477,7 @@

                                http-duplex.js

                                * @example <caption>Array of string value</caption> * request.setHeader('Set-Cookie', ['type=auth', 'language=javascript']); */ - + /** * Flushes all data buffered since cork() was called. * @method uncork @@ -493,19 +493,19 @@

                                http-duplex.js

                                * <p>*Note:* If write() is called either before writeHead() or writeHead() just hasn't been called, it will switch * modes and flush the implicit headers that may be waiting before parts of this chunk are sent.<p/> * Node will buffer up to the first chunk of the body. Any additional calls to write() may be buffered as well * for packet efficiency purposes.</p> - * Returns true if the entire data was flushed successfully to the kernel buffer. Returns false if all or part of + * Returns true if the entire data was flushed successfully to the kernel buffer. Returns false if all or part of * the data was buffered in memory. * @method write * @alias HttpDuplex.write * @memberof HttpDuplex * @param {(string|Buffer)} chunk chunk of data to send. - * @param {string} [encoding='utf8'] If chunk is a string, this specifies how to encode it into a byte stream. + * @param {String} [encoding='utf8'] If chunk is a string, this specifies how to encode it into a byte stream. * @param {function} [callback] Callback to call when this chunk of data is flushed. - * @returns {boolean} + * @returns {Boolean} * @emits {@link event:drain|drain} Emitted when data was buffered and the buffer has become free for use again. * @see {@link https://nodejs.org/api/http.html#http_response_write_chunk_encoding_callback|http.ServerResponse.write} */ - + /** * Sends an HTTP/1.1 100 Continue message to the client. * @method writeContinue @@ -520,24 +520,24 @@

                                http-duplex.js

                                * @method writeHead * @alias HttpDuplex.writeHead * @memberof HttpDuplex - * @param {number} statusCode 3-digit HTTP status code, like 404 - * @param {string} [statusMessage] An optional human readable status message to send with the status code - * @param {object} [headers] An object containing the response headers to send + * @param {Number} statusCode 3-digit HTTP status code, like 404 + * @param {String} [statusMessage] An optional human readable status message to send with the status code + * @param {Object} [headers] An object containing the response headers to send * @see {@link https://nodejs.org/api/http.html#http_response_writehead_statuscode_statusmessage_headers|response.writeHead} * @example var content = 'Under Construction...'; * response.writeHead(200, { * 'Content-Length': Buffer.byteLength(content), - * 'Content-Type': 'text/plain' + * 'Content-Type': 'text/plain' * }); * response.end(content); */ - + /** - * __Warning:__ This has been deprecated in node, __don't__ use it. Any apis that require this funtion should be + * __Warning:__ This has been deprecated in node, __don't__ use it. Any apis that require this funtion should be * updated to use writeHead insted. * @method writeHeader * @alias HttpDuplex.writeHeader - * @memberof HttpDuplex + * @memberof HttpDuplex * @deprecated {@link https://nodejs.org/api/deprecations.html#deprecations_dep0063_serverresponse_prototype_writeheader|Node Deprecated} * @see writeHead */ diff --git a/docs/code/index.html b/docs/code/index.html index 00942a6..e0081de 100644 --- a/docs/code/index.html +++ b/docs/code/index.html @@ -22,7 +22,7 @@
                                diff --git a/docs/code/module-lib_util.html b/docs/code/module-lib_util.html index 3188803..f97013f 100644 --- a/docs/code/module-lib_util.html +++ b/docs/code/module-lib_util.html @@ -22,7 +22,7 @@
                                @@ -168,7 +168,7 @@
                                Parameters:
                                -Object +http.IncomingMessage @@ -191,7 +191,7 @@
                                Parameters:
                                -Object +http.ServerResponse @@ -201,7 +201,7 @@
                                Parameters:
                                -

                                http response object

                                +

                                http response

                                @@ -263,7 +263,7 @@

                                (inner)
                                Source:
                                @@ -370,7 +370,7 @@

                                Parameters:
                                -Object +http.IncomingMessage @@ -393,7 +393,7 @@
                                Parameters:
                                -Object +http.ServerResponse @@ -403,7 +403,7 @@
                                Parameters:
                                -

                                http response object

                                +

                                http response

                                @@ -447,6 +447,231 @@
                                Returns:
                                + +

                                (inner) infoResponse(git, repo, service, req, res)

                                + + + + + + +
                                + + +
                                Source:
                                +
                                + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                                + + + + + +
                                +

                                sends http response using the appropriate output from service call

                                +
                                + + + + + + + + + + + +
                                Parameters:
                                + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                                NameTypeDescription
                                git + + +Git + + + +

                                an instance of git object

                                repo + + +String + + + +

                                the repository

                                service + + +String + + + +

                                the method that is responding infoResponse (push, pull, clone)

                                req + + +http.IncomingMessage + + + +

                                http request object

                                res + + +http.ServerResponse + + + +

                                http response

                                + + + + + + + + + + + + + + + + + + + +

                                (inner) noCache(res)

                                @@ -544,7 +769,7 @@
                                Parameters:
                                -Object +http.ServerResponse @@ -554,7 +779,7 @@
                                Parameters:
                                -

                                http response object

                                +

                                http response

                                @@ -749,7 +974,7 @@

                                (inner)
                                Source:
                                @@ -867,9 +1092,7 @@

                                Returns:
                                -
                                  -
                                • returns the name of the repo
                                • -
                                +

                                returns the name of the repo

                                @@ -890,6 +1113,208 @@
                                Returns:
                                + + + + +

                                (inner) serviceRespond(dup, service, repoLocation, res)

                                + + + + + + +
                                + + +
                                Source:
                                +
                                + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                                + + + + + +
                                +

                                execute given git operation and respond

                                +
                                + + + + + + + + + + + +
                                Parameters:
                                + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                                NameTypeDescription
                                dup + + +HttpDuplex + + + +

                                duplex object to catch errors

                                service + + +String + + + +

                                the method that is responding infoResponse (push, pull, clone)

                                repoLocation + + +String + + + +

                                the repo path on disk

                                res + + +http.ServerResponse + + + +

                                http response

                                + + + + + + + + + + + + + + + + + diff --git a/docs/code/service.js.html b/docs/code/service.js.html index f1c0f4d..e293e33 100644 --- a/docs/code/service.js.html +++ b/docs/code/service.js.html @@ -22,7 +22,7 @@
                                @@ -42,25 +42,21 @@

                                service.js

                                const zlib = require('zlib'); const { spawn } = require('child_process'); -const { inherits } = require('util'); - -const encodings = { - 'gzip': () => zlib.createGunzip(), - 'deflate': () => zlib.createDeflate() -}; const headerRE = { - 'receive-pack': '([0-9a-fA-F]+) ([0-9a-fA-F]+)' + - ' refs\/(heads|tags)\/(.*?)( |00|\u0000)' + // eslint-disable-line - '|^(0000)$', - 'upload-pack': '^\\S+ ([0-9a-fA-F]+)' + 'receive-pack': '([0-9a-fA-F]+) ([0-9a-fA-F]+) refs\/(heads|tags)\/(.*?)( |00|\u0000)|^(0000)$', // eslint-disable-line + 'upload-pack': '^\\S+ ([0-9a-fA-F]+)' }; -/** - * @class Service - * @extends HttpDuplex - */ class Service extends HttpDuplex { + /** + * Handles invoking the git-*-pack binaries + * @class Service + * @extends HttpDuplex + * @param {Object} opts - options to bootstrap the service object + * @param {http.IncomingMessage } req - http request object + * @param {http.ServerResponse} res - http response + */ constructor(opts, req, res) { super(req, res); @@ -77,7 +73,11 @@

                                service.js

                                // stream needed to receive data after decoding, but before accepting var ts = through(); - var decoder = encodings[req.headers['content-encoding']]; + var decoder = { + 'gzip': () => zlib.createGunzip(), + 'deflate': () => zlib.createDeflate() + }[req.headers['content-encoding']]; + if (decoder) { // data is compressed with gzip or deflate req.pipe(decoder()).pipe(ts).pipe(buffered); @@ -94,7 +94,7 @@

                                service.js

                                } } - ts.once('data', function(buf) { + ts.once('data', function onData(buf) { data += buf; var ops = data.match(new RegExp(headerRE[self.service], 'gi')); @@ -133,50 +133,52 @@

                                service.js

                                }); }); - self.once('accept', function() { + self.once('accept', function onAccept() { process.nextTick(function() { var cmd = ['git-' + opts.service, '--stateless-rpc', opts.cwd]; var ps = spawn(cmd[0], cmd.slice(1)); ps.on('error', function(err) { - self.emit('error', new Error( - err.message + ' running command ' + cmd.join(' ') - )); + self.emit('error', new Error(`${err.message} running command ${cmd.join(' ')}`)); }); self.emit('service', ps); var respStream = through(function(c) { - if (self.listeners('response').length === 0) - return this.queue(c); + if (self.listeners('response').length === 0) return this.queue(c); // prevent git from sending the close signal - if (c.length === 4 && c.toString() === '0000') - return; + if (c.length === 4 && c.toString() === '0000') return; this.queue(c); }, function() { - if (self.listeners('response').length > 0) - return; + if (self.listeners('response').length > 0) return; this.queue(null); }); - const endResponse = () => { + + self.emit('response', respStream, function endResponse() { res.queue(new Buffer('0000')); res.queue(null); - }; - - self.emit('response', respStream, endResponse); + }); ps.stdout.pipe(respStream).pipe(res); buffered.pipe(ps.stdin); buffered.resume(); + ps.on('exit', self.emit.bind(self, 'exit')); }); }); - self.once('reject', (code, msg) => { + self.once('reject', function onReject(code, msg) { res.statusCode = code; res.end(msg); }); } + /** + * reject request in flight + * @method reject + * @memberof Service + * @param {Number} code - http response code + * @param {String} msg - message that should be displayed on teh client + */ reject(code, msg) { if (this.status !== 'pending') return; @@ -187,16 +189,19 @@

                                service.js

                                this.status = 'rejected'; this.emit('reject', code || 500, msg); } - accept(dir) { + /** + * accepts request to access resource + * @method accept + * @memberof Service + */ + accept() { if (this.status !== 'pending') return; this.status = 'accepted'; - this.emit('accept', dir); + this.emit('accept'); } } -inherits(Service, HttpDuplex); - module.exports = Service;
                                diff --git a/docs/code/util.js.html b/docs/code/util.js.html index 685370e..fe4bd43 100644 --- a/docs/code/util.js.html +++ b/docs/code/util.js.html @@ -22,7 +22,7 @@
                                @@ -41,16 +41,16 @@

                                util.js

                                * @module lib/util */ -const httpDuplex = require('./http-duplex'); const { spawn } = require('child_process'); +const httpDuplex = require('./http-duplex'); const Service = require('./service'); const Util = { /** * adds headers to the response object to add cache control * @method noCache - * @param {Object} res - http response object + * @param {http.ServerResponse} res - http response */ noCache: function noCache(res) { res.setHeader('expires', 'Fri, 01 Jan 1980 00:00:00 GMT'); @@ -60,8 +60,8 @@

                                util.js

                                /** * sets and parses basic auth headers if they exist * @method basicAuth - * @param {Object} req - http request object - * @param {Object} res - http response object + * @param {http.IncomingMessage } req - http request object + * @param {http.ServerResponse} res - http response * @param {Function} callback - function(username, password, error) */ basicAuth: function basicAuth(req, res, callback) { @@ -87,8 +87,9 @@

                                util.js

                                * @param {Function} callback - function(code, signature) */ onExit: function onExit(ps, callback) { - var code, sig; - var pending = 3; + let code; + let sig; + let pending = 3; const onend = () => { if (--pending === 0) { @@ -105,7 +106,15 @@

                                util.js

                                ps.stdout.on('end', onend); ps.stderr.on('end', onend); }, - serviceRespond: function serviceRespond(self, service, file, res) { + /** + * execute given git operation and respond + * @method serviceRespond + * @param {HttpDuplex} dup - duplex object to catch errors + * @param {String} service - the method that is responding infoResponse (push, pull, clone) + * @param {String} repoLocation - the repo path on disk + * @param {http.ServerResponse} res - http response + */ + serviceRespond: function serviceRespond(dup, service, repoLocation, res) { const pack = (s) => { var n = (4 + s.length).toString(16); return Array(4 - n.length + 1).join('0') + n + s; @@ -114,20 +123,26 @@

                                util.js

                                res.write(pack('# service=git-' + service + '\n')); res.write('0000'); - const cmd = ['git-' + service, '--stateless-rpc', '--advertise-refs', file]; + const cmd = ['git-' + service, '--stateless-rpc', '--advertise-refs', repoLocation]; const ps = spawn(cmd[0], cmd.slice(1)); ps.on('error', (err) => { - self.emit('error', new Error( - err.message + ' running command ' + cmd.join(' ') - )); + dup.emit('error', new Error(`${err.message} running command ${cmd.join(' ')}`)); }); ps.stdout.pipe(res); }, - infoResponse: function infoResponse(opts, req, res) { - var self = opts.repos; + /** + * sends http response using the appropriate output from service call + * @method infoResponse + * @param {Git} git - an instance of git object + * @param {String} repo - the repository + * @param {String} service - the method that is responding infoResponse (push, pull, clone) + * @param {http.IncomingMessage } req - http request object + * @param {http.ServerResponse} res - http response + */ + infoResponse: function infoResponse(git, repo, service, req, res) { var dup = new httpDuplex(req, res); - dup.cwd = self.dirMap(opts.repo); - dup.repo = opts.repo; + dup.cwd = git.dirMap(repo); + dup.repo = repo; dup.accept = dup.emit.bind(dup, 'accept'); dup.reject = dup.emit.bind(dup, 'reject'); @@ -137,17 +152,17 @@

                                util.js

                                res.end(); }); - var anyListeners = self.listeners('info').length > 0; + var anyListeners = git.listeners('info').length > 0; - self.exists(opts.repo, (ex) => { + git.exists(repo, (ex) => { dup.exists = ex; - if (!ex && self.autoCreate) { + if (!ex && git.autoCreate) { dup.once('accept', () => { - self.create(opts.repo, next); + git.create(repo, next); }); - self.emit('info', dup); + git.emit('info', dup); if (!anyListeners) dup.accept(); } else if (!ex) { res.statusCode = 404; @@ -155,7 +170,7 @@

                                util.js

                                res.end('repository not found'); } else { dup.once('accept', next); - self.emit('info', dup); + git.emit('info', dup); if (!anyListeners) dup.accept(); } @@ -164,18 +179,22 @@

                                util.js

                                function next() { res.setHeader( 'content-type', - 'application/x-git-' + opts.service + '-advertisement' + 'application/x-git-' + service + '-advertisement' ); Util.noCache(res); - var d = self.dirMap(opts.repo); - Util.serviceRespond(self, opts.service, d, res); + Util.serviceRespond( + git, + service, + git.dirMap(repo), + res + ); } }, /** * parses a git string and returns the repo name * @method parseGitName * @param {String} repo - the raw repo name containing .git - * @return {String} - returns the name of the repo + * @return {String} returns the name of the repo */ parseGitName: function parseGitName(repo) { const locationOfGit = repo.lastIndexOf('.git'); @@ -185,8 +204,8 @@

                                util.js

                                * responds with the correct service depending on the action * @method createAction * @param {Object} opts - options to pass Service - * @param {Object} req - http request object - * @param {Object} res - http response object + * @param {http.IncomingMessage } req - http request object + * @param {http.ServerResponse} res - http response * @return {Service} */ createAction: function createAction(opts, req, res) { diff --git a/lib/git.js b/lib/git.js index 539a23d..7367544 100644 --- a/lib/git.js +++ b/lib/git.js @@ -13,14 +13,109 @@ const { parseGitName, createAction, infoResponse, onExit, basicAuth, noCache } = const services = ['upload-pack', 'receive-pack']; /** - * @class Git - */ + * @event Git#push + * @type {Object} + * @property {HttpDuplex} push - is a http duplex object (see below) with these extra properties + * @property {String} push.repo - the string that defines the repo + * @property {String} push.commit - the string that defines the commit sha + * @property {String} push.branch - the string that defines the branch + * @example + repos.on('push', function (push) { ... } + + Emitted when somebody does a `git push` to the repo. + + Exactly one listener must call `push.accept()` or `push.reject()`. If there are + no listeners, `push.accept()` is called automatically. + * +**/ + +/** + * @event Git#tag + * @type {Object} + * @property {HttpDuplex} tag - an http duplex object (see below) with these extra properties: + * @property {String} tag.repo - the string that defines the repo + * @property {String} tag.commit - the string that defines the commit sha + * @property {String} tag.version - the string that defines the repo + * @example + repos.on('tag', function (tag) { ... } + + Emitted when somebody does a `git push --tags` to the repo. + Exactly one listener must call `tag.accept()` or `tag.reject()`. If there are + No listeners, `tag.accept()` is called automatically. + * +**/ + +/** + * @event Git#fetch + * @type {Object} + * @property {HttpDuplex} fetch - an http duplex object (see below) with these extra properties: + * @property {String} fetch.repo - the string that defines the repo + * @property {String} fetch.commit - the string that defines the commit sha + * @example + repos.on('fetch', function (fetch) { ... } + + Emitted when somebody does a `git fetch` to the repo (which happens whenever you + do a `git pull` or a `git clone`). + + Exactly one listener must call `fetch.accept()` or `fetch.reject()`. If there are + no listeners, `fetch.accept()` is called automatically. + * +*/ + +/** + * @event Git#info + * @type {Object} + * @property {HttpDuplex} info - an http duplex object (see below) with these extra properties: + * @property {String} info.repo - the string that defines the repo + * @example + repos.on('info', function (info) { ... } + + Emitted when the repo is queried for info before doing other commands. + + Exactly one listener must call `info.accept()` or `info.reject()`. If there are + no listeners, `info.accept()` is called automatically. + * +*/ + +/** + * @event Git#info + * @type {Object} + * @property {HttpDuplex} info - an http duplex object (see below) with these extra properties: + * @property {String} info.repo - the string that defines the repo + * @example + repos.on('info', function (info) { ... } + + Emitted when the repo is queried for info before doing other commands. + + Exactly one listener must call `info.accept()` or `info.reject()`. If there are + no listeners, `info.accept()` is called automatically. + * +*/ + +/** + * @event Git#head + * @type {Object} + * @property {HttpDuplex} head - an http duplex object (see below) with these extra properties: + * @property {String} head.repo - the string that defines the repo + * @example + repos.on('head', function (head) { ... } + + Emitted when the repo is queried for HEAD before doing other commands. + + Exactly one listener must call `head.accept()` or `head.reject()`. If there are + no listeners, `head.accept()` is called automatically. + * +*/ + class Git extends EventEmitter { /** - * returns an instance of Git + * + * Handles invoking the git-*-pack binaries + * @class Git + * @extends EventEmitter * @param {(String|Function)} repoDir - Create a new repository collection from the directory `repoDir`. `repoDir` should be entirely empty except for git repo directories. If `repoDir` is a function, `repoDir(repo)` will be used to dynamically resolve project directories. The return value of `repoDir(repo)` should be a string path specifying where to put the string `repo`. Make sure to return the same value for `repo` every time since `repoDir(repo)` will be called multiple times. * @param {Object} options - options that can be applied on the new instance being created - * @param {Boolean=} options.autoCreate - By default, repository targets will be created if they don't exist. You can + * @param {Boolean=} options.autoCreate - By default, repository targets will be created if they don't exist. You can disable that behavior with `options.autoCreate = true` * @param {Function} options.authenticate - a function that has the following arguments (repo, username, password, next) and will be called when a request comes through if set * @@ -38,106 +133,8 @@ class Git extends EventEmitter { return reject("sorry you don't have access to this content"); }); } - * @param {Boolean=} options.checkout - If `opts.checkout` is true, create and expected checked-out repos instead of - bare repos - * @return {Git} - * @description - # events - - ## repos.on('push', function (push) { ... } - - Emitted when somebody does a `git push` to the repo. - - Exactly one listener must call `push.accept()` or `push.reject()`. If there are - no listeners, `push.accept()` is called automatically. - - `push` is an http duplex object (see below) with these extra properties: - - * push.repo - * push.commit - * push.branch - - ## repos.on('tag', function (tag) { ... } - - Emitted when somebody does a `git push --tags` to the repo. - - Exactly one listener must call `tag.accept()` or `tag.reject()`. If there are - no listeners, `tag.accept()` is called automatically. - - `tag` is an http duplex object (see below) with these extra properties: - - * tag.repo - * tag.commit - * tag.version - - ## repos.on('fetch', function (fetch) { ... } - - Emitted when somebody does a `git fetch` to the repo (which happens whenever you - do a `git pull` or a `git clone`). - - Exactly one listener must call `fetch.accept()` or `fetch.reject()`. If there are - no listeners, `fetch.accept()` is called automatically. - - `fetch` is an http duplex objects (see below) with these extra properties: - - * fetch.repo - * fetch.commit - - ## repos.on('info', function (info) { ... } - - Emitted when the repo is queried for info before doing other commands. - - Exactly one listener must call `info.accept()` or `info.reject()`. If there are - no listeners, `info.accept()` is called automatically. - - `info` is an http duplex object (see below) with these extra properties: - - * info.repo - - ## repos.on('head', function (head) { ... } - - Emitted when the repo is queried for HEAD before doing other commands. - - Exactly one listener must call `head.accept()` or `head.reject()`. If there are - no listeners, `head.accept()` is called automatically. - - `head` is an http duplex object (see below) with these extra properties: - - * head.repo - - ## push.on('response', function(response, done) { ... }) - - Emitted when node-git-server creates a response stream that will be sent to the git client on the other end. - - This should really only be used if you want to send verbose or error messages to the remote git client. - - `response` is a writable stream that can accept buffers containing git packfile sidechannel transfer protocol encoded strings. `done` is a callback that must be called when you want to end the response. - - If you create a response listener then you must either call the `done` function or execute the following end sequence when you want to end the response: - - ```js - response.queue(new Buffer('0000')) - response.queue(null) - ``` - - If you never use the response event then the above data will be sent by default. Binding a listener to the response event will prevent the end sequence those from being sent, so you must send them yourself after sending any other messages. - - # http duplex objects - - The arguments to each of the events `'push'`, `'fetch'`, `'info'`, and `'head'` are [http duplex](http://github.com/substack/http-duplex) that act as both http - server request and http server response objects so you can pipe to and from them. - - For every event if there are no listeners `dup.accept()` will be called - automatically. - - ## dup.accept() - - Accept the pending request. - - ## dup.reject() - - Reject the pending request. - */ + * @param {Boolean=} options.checkout - If `opts.checkout` is true, create and expected checked-out repos instead of bare repos + */ constructor(repoDir, options={}) { super(); @@ -156,6 +153,7 @@ class Git extends EventEmitter { /** * Get a list of all the repositories * @method list + * @memberof Git * @param {Function} callback function to be called when repositories have been found `function(error, repos)` */ list(callback) { @@ -171,6 +169,7 @@ class Git extends EventEmitter { /** * Find out whether `repoName` exists in the callback `cb(exists)`. * @method exists + * @memberof Git * @param {String} repo - name of the repo * @param {Function=} callback - function to be called when finished */ @@ -180,6 +179,7 @@ class Git extends EventEmitter { /** * Create a subdirectory `dir` in the repo dir with a callback `cb(err)`. * @method mkdir + * @memberof Git * @param {String} dir - directory name * @param {Function=} callback - callback to be called when finished */ @@ -197,6 +197,7 @@ class Git extends EventEmitter { /** * Create a new bare repository `repoName` in the instance repository directory. * @method create + * @memberof Git * @param {String} repo - the name of the repo * @param {Function=} callback - Optionally get a callback `cb(err)` to be notified when the repository was created. */ @@ -238,6 +239,7 @@ class Git extends EventEmitter { /** * Handle incoming HTTP requests with a connect-style middleware * @method handle + * @memberof Git * @param {Object} req - http request object * @param {Object} res - http response object */ @@ -276,11 +278,7 @@ class Git extends EventEmitter { res.end(error); return; } else { - return infoResponse({ - repos: self, - repo: repo, - service: service, - }, req, res); + return infoResponse(self, repo, service, req, res); } }; @@ -409,6 +407,13 @@ class Git extends EventEmitter { if (x === false) next(ix + 1); })(0); } + /** + * starts a git server on the given port + * @method listen + * @memberof Git + * @param {Number} port - the port to start the server on + * @param {Function} callback - the function to call when server is started or error has occured + */ listen(port, callback) { var self = this; this.server = http.createServer(function(req, res) { @@ -416,6 +421,11 @@ class Git extends EventEmitter { }); this.server.listen(port, callback); } + /** + * closes the server instance + * @method close + * @memberof Git + */ close() { this.server.close(); } diff --git a/lib/http-duplex.js b/lib/http-duplex.js index 39206c8..7cea0a1 100644 --- a/lib/http-duplex.js +++ b/lib/http-duplex.js @@ -1,7 +1,7 @@ const EventEmitter = require('events'); class HttpDuplex extends EventEmitter { - /** + /** * Constructs a proxy object over input and output resulting in a unified stream. * Generally meant to combine request and response streams in the http.request event * @class HttpDuplex @@ -22,8 +22,8 @@ class HttpDuplex extends EventEmitter { super(); /** - * A IncomingMessage created by http.Server or http.ClientRequest usually passed as the - * first parameter to the 'request' and 'response' events. Implements Readable Stream interface + * A IncomingMessage created by http.Server or http.ClientRequest usually passed as the + * first parameter to the 'request' and 'response' events. Implements Readable Stream interface * but may not be a decendant thereof. * @type {http.IncomingMessage} * @see {@link https://nodejs.org/api/http.html#http_event_request|request} @@ -74,14 +74,14 @@ class HttpDuplex extends EventEmitter { } /** - * Request/response headers. Key-value pairs of header names and values. Header names are always lower-case. + * Request/response headers. Key-value pairs of header names and values. Header names are always lower-case. * @name headers * @alias HttpDuplex.headers * @memberof HttpDuplex - * @type {object} + * @type {Object} * @readonly * @see {@link https://nodejs.org/api/http.html#http_message_headers|message.headers} - */ + */ get headers() { return this.req.headers; } @@ -91,7 +91,7 @@ class HttpDuplex extends EventEmitter { * @name httpVersion * @alias HttpDuplex.httpVersion * @memberof HttpDuplex - * @type {string} + * @type {String} * @see {@link https://nodejs.org/api/http.html#http_message_httpversion|message.httpVersion} * @readonly */ @@ -104,7 +104,7 @@ class HttpDuplex extends EventEmitter { * @name httpVersionMajor * @alias HttpDuplex.httpVersionMajor * @memberof HttpDuplex - * @type {number} + * @type {Number} * @see httpVersion * @readonly */ @@ -117,29 +117,29 @@ class HttpDuplex extends EventEmitter { * @name httpVersionMinor * @alias HttpDuplex.httpVersionMinor * @memberof HttpDuplex - * @type {string} + * @type {String} * @see httpVersion * @readonly */ get httpVersionMinor() { return this.req.httpVersionMinor; } - + /** * Request method of the incoming request. - * @type {string} + * @type {String} * @see {@link https://nodejs.org/api/http.html#http_event_request|request} * @see {@link https://nodejs.org/api/http.html#http_class_http_serverresponse|http.ServerResponse} * @example 'GET', 'DELETE' * @readonly - */ + */ get method() { return this.req.method; } /** * Is this stream readable. - * @type {boolean} + * @type {Boolean} * @readonly */ get readable() { @@ -148,7 +148,7 @@ class HttpDuplex extends EventEmitter { /** * net.Socket object associated with the connection. - * @type net.Socket + * @type net.Socket * @see {@link https://nodejs.org/api/net.html#net_class_net_socket|net.Socket} * @readonly */ @@ -157,8 +157,8 @@ class HttpDuplex extends EventEmitter { } /** - * The HTTP status code. Generally assigned before sending headers for a response to a client. - * @type {number} + * The HTTP status code. Generally assigned before sending headers for a response to a client. + * @type {Number} * @default 200 * @see {@link https://nodejs.org/api/http.html#http_response_statuscode|response.statusCode} * @example request.statusCode = 404; @@ -174,7 +174,7 @@ class HttpDuplex extends EventEmitter { /** * Controls the status message sent to the client as long as an explicit call to response.writeHead() isn't made * If ignored or the value is undefined, the default message corresponding to the status code will be used. - * @type {string} + * @type {String} * @default undefined * @see {@link https://nodejs.org/api/http.html#http_response_statusmessage|response.statusMessage} * @example request.statusMessage = 'Document Not found'; @@ -188,12 +188,12 @@ class HttpDuplex extends EventEmitter { } /** - * Request/response trailer headers. Just like {@link headers} except these are only written + * Request/response trailer headers. Just like {@link headers} except these are only written * after the initial response to the client. - * This object is only populated at the 'end' event and only work if a 'transfer-encoding: chunked' + * This object is only populated at the 'end' event and only work if a 'transfer-encoding: chunked' * header is sent in the initial response. * @name HttpDuplex#trailers - * @type {object} + * @type {Object} * @readonly * @see headers * @see addTrailers @@ -206,7 +206,7 @@ class HttpDuplex extends EventEmitter { /** * Whether or not the client connection has been upgraded - * @type {boolean} + * @type {Boolean} * @see {@link https://nodejs.org/api/http.html#http_event_upgrade_1|upgrade} * @readonly */ @@ -218,9 +218,9 @@ class HttpDuplex extends EventEmitter { * Request URL string. * @example A request made as: * GET /info?check=none HTTP/1.1 - * @example Will return the string + * @example Will return the string * '/info?check=none' - * @type {string} + * @type {String} * @readonly */ get url() { @@ -280,14 +280,14 @@ module.exports = HttpDuplex; * passed as a Buffer. * @event data * @alias HttpDuplex#event:data - * @param {(string|buffer|any)} chunk The chunk is either a buffer or string when the stream isn't operating + * @param {(string|buffer|any)} chunk The chunk is either a buffer or string when the stream isn't operating * in object mode. When the stream is in object mode, the chunk can be any JavaScript value other than null. * @memberof HttpDuplex * @see {@link https://nodejs.org/api/stream.html#stream_event_data|stream.Readable/data} */ /** - * If a call to response.write(chunk) returns false, the drain event will be emitted once it is appropriate to + * If a call to response.write(chunk) returns false, the drain event will be emitted once it is appropriate to * resume writing data to the stream. * @event drain * @alias HttpDuplex#event:drain @@ -322,12 +322,12 @@ module.exports = HttpDuplex; /** * Adds trailing headers to the response. - * Trailers will only be emitted if chunked encoding is enabled for the response; otherwise they are discarded. + * Trailers will only be emitted if chunked encoding is enabled for the response; otherwise they are discarded. * @method addTrailers * @name addTrailers * @alias HttpDuplex.addTrailers * @memberof HttpDuplex - * @param {object} headers Trailing headers to add to the response + * @param {Object} headers Trailing headers to add to the response * @see trailers * @see headers * @see {@link https://nodejs.org/api/http.html#http_message_trailers|message.trailers} @@ -357,7 +357,7 @@ module.exports = HttpDuplex; * @alias HttpDuplex.end * @memberof HttpDuplex * @param {(string|Buffer)} data optional data to write to the response before closing the connection - * @param {string} encoding Encoding that should be used to write the data + * @param {String} encoding Encoding that should be used to write the data * @param {function} callback Function to be called once the response stream is finished * @see {@link https://nodejs.org/api/http.html#http_response_end_data_encoding_callback|response.end} */ @@ -367,28 +367,28 @@ module.exports = HttpDuplex; * @method getHeader * @alias HttpDuplex.getHeader * @memberof HttpDuplex - * @param {string} name Header to get the value of - * @returns {string} + * @param {String} name Header to get the value of + * @returns {String} * @see {@link https://nodejs.org/api/http.html#http_request_getheader_name|getHeader} * @example * let contentType = request.getHeader('Content-Type'); */ /** - * Switch readable stream out of flowing mode and stop emitting 'data' events. + * Switch readable stream out of flowing mode and stop emitting 'data' events. * Any new data that becomes available during this time will stay buffered until resume is called. * @method pause * @alias HttpDuplex.pause * @memberof HttpDuplex * @see {@link https://nodejs.org/api/stream.html#stream_readable_pause|stream.Readable.pause} */ - + /** * Remove a header from the response headers. * @method removeHeader * @alias HttpDuplex.removeHeader * @memberof HttpDuplex - * @param {string} name Header to remove + * @param {String} name Header to remove * @see {@link https://nodejs.org/api/http.html#http_request_removeheader_name|removeHeader} * @example * request.removeHeader('Content-Type'); @@ -402,13 +402,13 @@ module.exports = HttpDuplex; * @memberof HttpDuplex * @see {@link https://nodejs.org/api/stream.html#stream_readable_resume|stream.Readable.resume} */ - + /** * Sets the character encoding for data written to the stream. * @method setDefaultEncoding * @alias HttpDuplex.setDefaultEncoding * @memberof HttpDuplex - * @param encoding {string} new character encoding + * @param encoding {String} new character encoding * @see setEncoding * @example request.setDefaultEncoding('utf8'); */ @@ -418,18 +418,18 @@ module.exports = HttpDuplex; * @method setEncoding * @alias HttpDuplex.setEncoding * @memberof HttpDuplex - * @param encoding {string} new character encoding + * @param encoding {String} new character encoding * @see setDefaultEncoding - * @example request.setEncoding('utf8'); + * @example request.setEncoding('utf8'); */ /** - * Set a single header. If the header already exists, it will be replaced. + * Set a single header. If the header already exists, it will be replaced. * It's possible to use an array of strings in place of value to send multiple headers with the same name. * @method setHeader * @alias HttpDuplex.setHeader * @memberof HttpDuplex - * @param {string} name Header to set + * @param {String} name Header to set * @param {string|string[]} value Value(s) to assign to header * @see removeHeader * @see {@link https://nodejs.org/api/http.html#http_request_setheader_name_value|setHeader} @@ -438,7 +438,7 @@ module.exports = HttpDuplex; * @example Array of string value * request.setHeader('Set-Cookie', ['type=auth', 'language=javascript']); */ - + /** * Flushes all data buffered since cork() was called. * @method uncork @@ -454,19 +454,19 @@ module.exports = HttpDuplex; *

                                *Note:* If write() is called either before writeHead() or writeHead() just hasn't been called, it will switch * modes and flush the implicit headers that may be waiting before parts of this chunk are sent.

                                * Node will buffer up to the first chunk of the body. Any additional calls to write() may be buffered as well * for packet efficiency purposes.

                                - * Returns true if the entire data was flushed successfully to the kernel buffer. Returns false if all or part of + * Returns true if the entire data was flushed successfully to the kernel buffer. Returns false if all or part of * the data was buffered in memory. * @method write * @alias HttpDuplex.write * @memberof HttpDuplex * @param {(string|Buffer)} chunk chunk of data to send. - * @param {string} [encoding='utf8'] If chunk is a string, this specifies how to encode it into a byte stream. + * @param {String} [encoding='utf8'] If chunk is a string, this specifies how to encode it into a byte stream. * @param {function} [callback] Callback to call when this chunk of data is flushed. - * @returns {boolean} + * @returns {Boolean} * @emits {@link event:drain|drain} Emitted when data was buffered and the buffer has become free for use again. * @see {@link https://nodejs.org/api/http.html#http_response_write_chunk_encoding_callback|http.ServerResponse.write} */ - + /** * Sends an HTTP/1.1 100 Continue message to the client. * @method writeContinue @@ -481,24 +481,24 @@ module.exports = HttpDuplex; * @method writeHead * @alias HttpDuplex.writeHead * @memberof HttpDuplex - * @param {number} statusCode 3-digit HTTP status code, like 404 - * @param {string} [statusMessage] An optional human readable status message to send with the status code - * @param {object} [headers] An object containing the response headers to send + * @param {Number} statusCode 3-digit HTTP status code, like 404 + * @param {String} [statusMessage] An optional human readable status message to send with the status code + * @param {Object} [headers] An object containing the response headers to send * @see {@link https://nodejs.org/api/http.html#http_response_writehead_statuscode_statusmessage_headers|response.writeHead} * @example var content = 'Under Construction...'; * response.writeHead(200, { * 'Content-Length': Buffer.byteLength(content), - * 'Content-Type': 'text/plain' + * 'Content-Type': 'text/plain' * }); * response.end(content); */ - + /** - * __Warning:__ This has been deprecated in node, __don't__ use it. Any apis that require this funtion should be + * __Warning:__ This has been deprecated in node, __don't__ use it. Any apis that require this funtion should be * updated to use writeHead insted. * @method writeHeader * @alias HttpDuplex.writeHeader - * @memberof HttpDuplex + * @memberof HttpDuplex * @deprecated {@link https://nodejs.org/api/deprecations.html#deprecations_dep0063_serverresponse_prototype_writeheader|Node Deprecated} * @see writeHead */ diff --git a/lib/service.js b/lib/service.js index 2f78245..0f50176 100644 --- a/lib/service.js +++ b/lib/service.js @@ -3,25 +3,21 @@ const HttpDuplex = require('./http-duplex'); const zlib = require('zlib'); const { spawn } = require('child_process'); -const { inherits } = require('util'); - -const encodings = { - 'gzip': () => zlib.createGunzip(), - 'deflate': () => zlib.createDeflate() -}; const headerRE = { - 'receive-pack': '([0-9a-fA-F]+) ([0-9a-fA-F]+)' + - ' refs\/(heads|tags)\/(.*?)( |00|\u0000)' + // eslint-disable-line - '|^(0000)$', - 'upload-pack': '^\\S+ ([0-9a-fA-F]+)' + 'receive-pack': '([0-9a-fA-F]+) ([0-9a-fA-F]+) refs\/(heads|tags)\/(.*?)( |00|\u0000)|^(0000)$', // eslint-disable-line + 'upload-pack': '^\\S+ ([0-9a-fA-F]+)' }; -/** - * @class Service - * @extends HttpDuplex - */ class Service extends HttpDuplex { + /** + * Handles invoking the git-*-pack binaries + * @class Service + * @extends HttpDuplex + * @param {Object} opts - options to bootstrap the service object + * @param {http.IncomingMessage } req - http request object + * @param {http.ServerResponse} res - http response + */ constructor(opts, req, res) { super(req, res); @@ -38,7 +34,11 @@ class Service extends HttpDuplex { // stream needed to receive data after decoding, but before accepting var ts = through(); - var decoder = encodings[req.headers['content-encoding']]; + var decoder = { + 'gzip': () => zlib.createGunzip(), + 'deflate': () => zlib.createDeflate() + }[req.headers['content-encoding']]; + if (decoder) { // data is compressed with gzip or deflate req.pipe(decoder()).pipe(ts).pipe(buffered); @@ -55,7 +55,7 @@ class Service extends HttpDuplex { } } - ts.once('data', function(buf) { + ts.once('data', function onData(buf) { data += buf; var ops = data.match(new RegExp(headerRE[self.service], 'gi')); @@ -94,50 +94,52 @@ class Service extends HttpDuplex { }); }); - self.once('accept', function() { + self.once('accept', function onAccept() { process.nextTick(function() { var cmd = ['git-' + opts.service, '--stateless-rpc', opts.cwd]; var ps = spawn(cmd[0], cmd.slice(1)); ps.on('error', function(err) { - self.emit('error', new Error( - err.message + ' running command ' + cmd.join(' ') - )); + self.emit('error', new Error(`${err.message} running command ${cmd.join(' ')}`)); }); self.emit('service', ps); var respStream = through(function(c) { - if (self.listeners('response').length === 0) - return this.queue(c); + if (self.listeners('response').length === 0) return this.queue(c); // prevent git from sending the close signal - if (c.length === 4 && c.toString() === '0000') - return; + if (c.length === 4 && c.toString() === '0000') return; this.queue(c); }, function() { - if (self.listeners('response').length > 0) - return; + if (self.listeners('response').length > 0) return; this.queue(null); }); - const endResponse = () => { + + self.emit('response', respStream, function endResponse() { res.queue(new Buffer('0000')); res.queue(null); - }; - - self.emit('response', respStream, endResponse); + }); ps.stdout.pipe(respStream).pipe(res); buffered.pipe(ps.stdin); buffered.resume(); + ps.on('exit', self.emit.bind(self, 'exit')); }); }); - self.once('reject', (code, msg) => { + self.once('reject', function onReject(code, msg) { res.statusCode = code; res.end(msg); }); } + /** + * reject request in flight + * @method reject + * @memberof Service + * @param {Number} code - http response code + * @param {String} msg - message that should be displayed on teh client + */ reject(code, msg) { if (this.status !== 'pending') return; @@ -148,14 +150,17 @@ class Service extends HttpDuplex { this.status = 'rejected'; this.emit('reject', code || 500, msg); } - accept(dir) { + /** + * accepts request to access resource + * @method accept + * @memberof Service + */ + accept() { if (this.status !== 'pending') return; this.status = 'accepted'; - this.emit('accept', dir); + this.emit('accept'); } } -inherits(Service, HttpDuplex); - module.exports = Service; diff --git a/lib/util.js b/lib/util.js index cd70cdb..3d187d3 100644 --- a/lib/util.js +++ b/lib/util.js @@ -2,16 +2,16 @@ * @module lib/util */ -const httpDuplex = require('./http-duplex'); const { spawn } = require('child_process'); +const httpDuplex = require('./http-duplex'); const Service = require('./service'); const Util = { /** * adds headers to the response object to add cache control * @method noCache - * @param {Object} res - http response object + * @param {http.ServerResponse} res - http response */ noCache: function noCache(res) { res.setHeader('expires', 'Fri, 01 Jan 1980 00:00:00 GMT'); @@ -21,8 +21,8 @@ const Util = { /** * sets and parses basic auth headers if they exist * @method basicAuth - * @param {Object} req - http request object - * @param {Object} res - http response object + * @param {http.IncomingMessage } req - http request object + * @param {http.ServerResponse} res - http response * @param {Function} callback - function(username, password, error) */ basicAuth: function basicAuth(req, res, callback) { @@ -48,8 +48,9 @@ const Util = { * @param {Function} callback - function(code, signature) */ onExit: function onExit(ps, callback) { - var code, sig; - var pending = 3; + let code; + let sig; + let pending = 3; const onend = () => { if (--pending === 0) { @@ -66,7 +67,15 @@ const Util = { ps.stdout.on('end', onend); ps.stderr.on('end', onend); }, - serviceRespond: function serviceRespond(self, service, file, res) { + /** + * execute given git operation and respond + * @method serviceRespond + * @param {HttpDuplex} dup - duplex object to catch errors + * @param {String} service - the method that is responding infoResponse (push, pull, clone) + * @param {String} repoLocation - the repo path on disk + * @param {http.ServerResponse} res - http response + */ + serviceRespond: function serviceRespond(dup, service, repoLocation, res) { const pack = (s) => { var n = (4 + s.length).toString(16); return Array(4 - n.length + 1).join('0') + n + s; @@ -75,20 +84,26 @@ const Util = { res.write(pack('# service=git-' + service + '\n')); res.write('0000'); - const cmd = ['git-' + service, '--stateless-rpc', '--advertise-refs', file]; + const cmd = ['git-' + service, '--stateless-rpc', '--advertise-refs', repoLocation]; const ps = spawn(cmd[0], cmd.slice(1)); ps.on('error', (err) => { - self.emit('error', new Error( - err.message + ' running command ' + cmd.join(' ') - )); + dup.emit('error', new Error(`${err.message} running command ${cmd.join(' ')}`)); }); ps.stdout.pipe(res); }, - infoResponse: function infoResponse(opts, req, res) { - var self = opts.repos; + /** + * sends http response using the appropriate output from service call + * @method infoResponse + * @param {Git} git - an instance of git object + * @param {String} repo - the repository + * @param {String} service - the method that is responding infoResponse (push, pull, clone) + * @param {http.IncomingMessage } req - http request object + * @param {http.ServerResponse} res - http response + */ + infoResponse: function infoResponse(git, repo, service, req, res) { var dup = new httpDuplex(req, res); - dup.cwd = self.dirMap(opts.repo); - dup.repo = opts.repo; + dup.cwd = git.dirMap(repo); + dup.repo = repo; dup.accept = dup.emit.bind(dup, 'accept'); dup.reject = dup.emit.bind(dup, 'reject'); @@ -98,17 +113,17 @@ const Util = { res.end(); }); - var anyListeners = self.listeners('info').length > 0; + var anyListeners = git.listeners('info').length > 0; - self.exists(opts.repo, (ex) => { + git.exists(repo, (ex) => { dup.exists = ex; - if (!ex && self.autoCreate) { + if (!ex && git.autoCreate) { dup.once('accept', () => { - self.create(opts.repo, next); + git.create(repo, next); }); - self.emit('info', dup); + git.emit('info', dup); if (!anyListeners) dup.accept(); } else if (!ex) { res.statusCode = 404; @@ -116,7 +131,7 @@ const Util = { res.end('repository not found'); } else { dup.once('accept', next); - self.emit('info', dup); + git.emit('info', dup); if (!anyListeners) dup.accept(); } @@ -125,18 +140,22 @@ const Util = { function next() { res.setHeader( 'content-type', - 'application/x-git-' + opts.service + '-advertisement' + 'application/x-git-' + service + '-advertisement' ); Util.noCache(res); - var d = self.dirMap(opts.repo); - Util.serviceRespond(self, opts.service, d, res); + Util.serviceRespond( + git, + service, + git.dirMap(repo), + res + ); } }, /** * parses a git string and returns the repo name * @method parseGitName * @param {String} repo - the raw repo name containing .git - * @return {String} - returns the name of the repo + * @return {String} returns the name of the repo */ parseGitName: function parseGitName(repo) { const locationOfGit = repo.lastIndexOf('.git'); @@ -146,8 +165,8 @@ const Util = { * responds with the correct service depending on the action * @method createAction * @param {Object} opts - options to pass Service - * @param {Object} req - http request object - * @param {Object} res - http response object + * @param {http.IncomingMessage } req - http request object + * @param {http.ServerResponse} res - http response * @return {Service} */ createAction: function createAction(opts, req, res) { diff --git a/test/httpduplex.js b/test/http-duplex.js similarity index 93% rename from test/httpduplex.js rename to test/http-duplex.js index 94b6b4e..542a8cb 100644 --- a/test/httpduplex.js +++ b/test/http-duplex.js @@ -11,11 +11,11 @@ Object.prototype.serialize = function() { Object.prototype.filterKeys = function(key) { var obj = this; - Object.keys(this).forEach(function (i) { + Object.keys(this).forEach(function (i) { if (i == key) delete obj[i]; }); - + return obj; }; @@ -52,7 +52,7 @@ var server = http.createServer(function (req, res) { dup.end(size + '\n'); }); } - else fs.createReadStream(__filename).pipe(dup); + else fs.createReadStream(__filename).pipe(dup); break; case '/info': if (dup.method == 'GET') { @@ -73,7 +73,7 @@ var server = http.createServer(function (req, res) { "Client: {12}\n" + "Socket: {13}\n" ).format ( - dup.method, + dup.method, dup.url, dup.statusCode, dup.upgrade, @@ -101,7 +101,7 @@ var server = http.createServer(function (req, res) { } }); -test(function (t) { +test('http-duplex', (t) => { t.plan(3); server.listen(0); @@ -126,16 +126,16 @@ test(function (t) { "Status: 200\n" + "Upgrade: false\n" + "Http Version 1: 1.1\n" + - "Http Version 2: 1.1\n" + + "Http Version 2: 1.1\n" + "Headers: \n" + "{\n" + " \"host\": \"localhost:" + server.address().port + "\",\n" + " \"connection\": \"close\"\n" + - "}\n" + + "}\n" + "Trailers: {}\n" + - "Complete: false\n" + + "Complete: false\n" + "Readable: true\n" + - "Writeable: {10}\n" + + "Writeable: {10}\n" + "Connection: [object Object]\n" + "Client: [object Object]\n" + "Socket: [object Object]\n"; From faad289b2acbd36df32ef39cc8bd9604e91bd710 Mon Sep 17 00:00:00 2001 From: echopoint <32996843+echopoint@users.noreply.github.com> Date: Fri, 10 Nov 2017 22:28:09 +0000 Subject: [PATCH 02/26] updates duplex lib to fix cork,uncork and change the behavior of writeHead slightly. These three methods are now chainable. (#27) --- lib/http-duplex.js | 97 ++++++++++++++++++++++++++-------------------- 1 file changed, 56 insertions(+), 41 deletions(-) diff --git a/lib/http-duplex.js b/lib/http-duplex.js index 7cea0a1..aacb9d3 100644 --- a/lib/http-duplex.js +++ b/lib/http-duplex.js @@ -231,6 +231,61 @@ class HttpDuplex extends EventEmitter { get writable() { return this.res.writable; } + + /** + * Sends a response header to the client request. Must only be called one time and before calling response.end(). + * @method writeHead + * @alias HttpDuplex.writeHead + * @memberof HttpDuplex + * @param {number} statusCode 3-digit HTTP status code, like 404 + * @param {string} [statusMessage] An optional human readable status message to send with the status code + * @param {object} [headers] An object containing the response headers to send + * @returns {this} + * @see {@link https://nodejs.org/api/http.html#http_response_writehead_statuscode_statusmessage_headers|response.writeHead} + * @example var content = 'Under Construction...'; + * response.writeHead(200, { + * 'Content-Length': Buffer.byteLength(content), + * 'Content-Type': 'text/plain' + * }); + * response.end(content); + */ + writeHead(statusCode, statusMessage, headers) { + this.res.writeHead(statusCode, statusMessage, headers); + return this; + } + + /** + * Buffers written data in memory. This data will be flushed when either the uncork or end methods are called. + * @method cork + * @alias HttpDuplex.cork + * @memberof HttpDuplex + * @returns {this} + * @see uncork + * @see {@link https://nodejs.org/api/stream.html#stream_writable_cork|stream.Writeable.cork} + * @example + * request.cork(); + * request.write('buffer data '); + * request.write('before sending '); + * request.uncork(); + */ + cork() { + this.res.connection.cork(); + return this; + } + + /** + * Flushes all data buffered since cork() was called. + * @method uncork + * @alias HttpDuplex.cork + * @memberof HttpDuplex + * @returns {this} + * @see cork + * @see {@link https://nodejs.org/api/stream.html#stream_writable_uncork|stream.Writeable.uncork} + */ + uncork() { + this.res.connection.uncork(); + return this; + } } // proxy request methods @@ -242,7 +297,7 @@ class HttpDuplex extends EventEmitter { // proxy respone methods [ - 'cork', 'uncork', 'setDefaultEncoding', 'write', 'end', 'flush', 'writeHeader', 'writeHead', 'writeContinue', + 'setDefaultEncoding', 'write', 'end', 'flush', 'writeHeader', 'writeContinue', 'setHeader', 'getHeader', 'removeHeader', 'addTrailers' ].forEach(function (name) { HttpDuplex.prototype[name] = function () { @@ -334,20 +389,6 @@ module.exports = HttpDuplex; * @see {@link https://nodejs.org/api/http.html#http_response_addtrailers_headers|response.addTrailers} */ -/** - * Buffers written data in memory. This data will be flushed when either the uncork or end methods are called. - * @method cork - * @alias HttpDuplex.cork - * @memberof HttpDuplex - * @see uncork - * @see {@link https://nodejs.org/api/stream.html#stream_writable_cork|stream.Writeable.cork} - * @example - * request.cork(); - * request.write('buffer data '); - * request.write('before sending '); - * request.uncork(); - */ - /** * Tells the server the response headers and body have been sent and that the message should be considered complete. * This MUST be called on every response. @@ -439,15 +480,6 @@ module.exports = HttpDuplex; * request.setHeader('Set-Cookie', ['type=auth', 'language=javascript']); */ -/** - * Flushes all data buffered since cork() was called. - * @method uncork - * @alias HttpDuplex.cork - * @memberof HttpDuplex - * @see cork - * @see {@link https://nodejs.org/api/stream.html#stream_writable_uncork|stream.Writeable.uncork} - */ - /** * Sends a chunk of the response body. This method may be called multiple times to provide successive parts of the * body. @@ -476,23 +508,6 @@ module.exports = HttpDuplex; * {@link https://nodejs.org/api/http.html#http_event_checkcontinue|http.Server/checkContinue} */ -/** - * Sends a response header to the client request. Must only be called one time and before calling response.end(). - * @method writeHead - * @alias HttpDuplex.writeHead - * @memberof HttpDuplex - * @param {Number} statusCode 3-digit HTTP status code, like 404 - * @param {String} [statusMessage] An optional human readable status message to send with the status code - * @param {Object} [headers] An object containing the response headers to send - * @see {@link https://nodejs.org/api/http.html#http_response_writehead_statuscode_statusmessage_headers|response.writeHead} - * @example var content = 'Under Construction...'; - * response.writeHead(200, { - * 'Content-Length': Buffer.byteLength(content), - * 'Content-Type': 'text/plain' - * }); - * response.end(content); - */ - /** * __Warning:__ This has been deprecated in node, __don't__ use it. Any apis that require this funtion should be * updated to use writeHead insted. From d9aabdfb8405e3cc6c84e30f6960e38f83dd8d9b Mon Sep 17 00:00:00 2001 From: Gabriel Csapo Date: Fri, 10 Nov 2017 14:29:50 -0800 Subject: [PATCH 03/26] 0.3.4 - updates duplex lib to fix cork, uncork and add some chaining - adds extensive docs to Git, Util and Service - adds named function to events to trace errors more easily --- CHANGELOG.md | 3 +- docs/code/Git.html | 4 +- docs/code/HttpDuplex.html | 110 +++++++++++++++++++++++++--------- docs/code/http-duplex.js.html | 97 +++++++++++++++++------------- package.json | 2 +- 5 files changed, 143 insertions(+), 73 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3ef6d8..8d8e1ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ -# Unreleased +# 0.3.4 (11/10/2017) +- updates duplex lib to fix cork, uncork and add some chaining - adds extensive docs to Git, Util and Service - adds named function to events to trace errors more easily diff --git a/docs/code/Git.html b/docs/code/Git.html index 68653ba..657d147 100644 --- a/docs/code/Git.html +++ b/docs/code/Git.html @@ -1882,7 +1882,7 @@

                                info

                                Source:
                                @@ -2082,7 +2082,7 @@

                                info

                                Source:
                                diff --git a/docs/code/HttpDuplex.html b/docs/code/HttpDuplex.html index 3bc0df1..11cba24 100644 --- a/docs/code/HttpDuplex.html +++ b/docs/code/HttpDuplex.html @@ -1528,7 +1528,7 @@

                                addTrailer
                                Source:
                                @@ -1663,7 +1663,7 @@

                                Parameters:
                                -

                                cork()

                                +

                                cork() → {this}

                                @@ -1675,7 +1675,7 @@

                                corkSource:
                                @@ -1757,6 +1757,24 @@
                                Example
                                +
                                Returns:
                                + + + + +
                                +
                                + Type +
                                +
                                + +this + + +
                                +
                                + + @@ -1776,7 +1794,7 @@

                                destroySource:
                                @@ -1860,7 +1878,7 @@

                                endSource:
                                @@ -2052,7 +2070,7 @@

                                getHeaderSource:
                                @@ -2215,7 +2233,7 @@

                                pauseSource:
                                @@ -2307,7 +2325,7 @@

                                removeHea
                                Source:
                                @@ -2452,7 +2470,7 @@

                                resumeSource:
                                @@ -2544,7 +2562,7 @@

                                set
                                Source:
                                @@ -2689,7 +2707,7 @@

                                setEncodin
                                Source:
                                @@ -2834,7 +2852,7 @@

                                setHeaderSource:
                                @@ -3002,7 +3020,7 @@
                                Parameters:
                                -

                                uncork()

                                +

                                uncork() → {this}

                                @@ -3014,7 +3032,7 @@

                                uncorkSource:
                                @@ -3088,6 +3106,24 @@

                                uncorkReturns:

                                + + + + +
                                +
                                + Type +
                                +
                                + +this + + +
                                +
                                + + @@ -3107,7 +3143,7 @@

                                writeSource:
                                @@ -3371,7 +3407,7 @@

                                writeCon
                                Source:
                                @@ -3451,7 +3487,7 @@

                                writeCon -

                                writeHead(statusCode, statusMessageopt, headersopt)

                                +

                                writeHead(statusCode, statusMessageopt, headersopt) → {this}

                                @@ -3463,7 +3499,7 @@

                                writeHeadSource:
                                @@ -3526,7 +3562,7 @@
                                Example
                                var content = 'Under Construction...';
                                 response.writeHead(200, {
                                     'Content-Length': Buffer.byteLength(content),
                                -    'Content-Type': 'text/plain'
                                +    'Content-Type': 'text/plain' 
                                 });
                                 response.end(content);
                                @@ -3566,7 +3602,7 @@
                                Parameters:
                                -Number +number @@ -3597,7 +3633,7 @@
                                Parameters:
                                -String +string @@ -3630,7 +3666,7 @@
                                Parameters:
                                -Object +object @@ -3670,6 +3706,24 @@
                                Parameters:
                                +
                                Returns:
                                + + + + +
                                +
                                + Type +
                                +
                                + +this + + +
                                +
                                + + @@ -3689,7 +3743,7 @@

                                writeHeade
                                Source:
                                @@ -3791,7 +3845,7 @@

                                close

                                Source:
                                @@ -3884,7 +3938,7 @@

                                data

                                Source:
                                @@ -4035,7 +4089,7 @@

                                drain

                                Source:
                                @@ -4127,7 +4181,7 @@

                                end

                                Source:
                                @@ -4219,7 +4273,7 @@

                                error

                                Source:
                                diff --git a/docs/code/http-duplex.js.html b/docs/code/http-duplex.js.html index c0e7423..c571ed9 100644 --- a/docs/code/http-duplex.js.html +++ b/docs/code/http-duplex.js.html @@ -270,6 +270,61 @@

                                http-duplex.js

                                get writable() { return this.res.writable; } + + /** + * Sends a response header to the client request. Must only be called one time and before calling response.end(). + * @method writeHead + * @alias HttpDuplex.writeHead + * @memberof HttpDuplex + * @param {number} statusCode 3-digit HTTP status code, like 404 + * @param {string} [statusMessage] An optional human readable status message to send with the status code + * @param {object} [headers] An object containing the response headers to send + * @returns {this} + * @see {@link https://nodejs.org/api/http.html#http_response_writehead_statuscode_statusmessage_headers|response.writeHead} + * @example var content = 'Under Construction...'; + * response.writeHead(200, { + * 'Content-Length': Buffer.byteLength(content), + * 'Content-Type': 'text/plain' + * }); + * response.end(content); + */ + writeHead(statusCode, statusMessage, headers) { + this.res.writeHead(statusCode, statusMessage, headers); + return this; + } + + /** + * Buffers written data in memory. This data will be flushed when either the uncork or end methods are called. + * @method cork + * @alias HttpDuplex.cork + * @memberof HttpDuplex + * @returns {this} + * @see uncork + * @see {@link https://nodejs.org/api/stream.html#stream_writable_cork|stream.Writeable.cork} + * @example + * request.cork(); + * request.write('buffer data '); + * request.write('before sending '); + * request.uncork(); + */ + cork() { + this.res.connection.cork(); + return this; + } + + /** + * Flushes all data buffered since cork() was called. + * @method uncork + * @alias HttpDuplex.cork + * @memberof HttpDuplex + * @returns {this} + * @see cork + * @see {@link https://nodejs.org/api/stream.html#stream_writable_uncork|stream.Writeable.uncork} + */ + uncork() { + this.res.connection.uncork(); + return this; + } } // proxy request methods @@ -281,7 +336,7 @@

                                http-duplex.js

                                // proxy respone methods [ - 'cork', 'uncork', 'setDefaultEncoding', 'write', 'end', 'flush', 'writeHeader', 'writeHead', 'writeContinue', + 'setDefaultEncoding', 'write', 'end', 'flush', 'writeHeader', 'writeContinue', 'setHeader', 'getHeader', 'removeHeader', 'addTrailers' ].forEach(function (name) { HttpDuplex.prototype[name] = function () { @@ -373,20 +428,6 @@

                                http-duplex.js

                                * @see {@link https://nodejs.org/api/http.html#http_response_addtrailers_headers|response.addTrailers} */ -/** - * Buffers written data in memory. This data will be flushed when either the uncork or end methods are called. - * @method cork - * @alias HttpDuplex.cork - * @memberof HttpDuplex - * @see uncork - * @see {@link https://nodejs.org/api/stream.html#stream_writable_cork|stream.Writeable.cork} - * @example - * request.cork(); - * request.write('buffer data '); - * request.write('before sending '); - * request.uncork(); - */ - /** * Tells the server the response headers and body have been sent and that the message should be considered complete. * This MUST be called on every response. @@ -478,15 +519,6 @@

                                http-duplex.js

                                * request.setHeader('Set-Cookie', ['type=auth', 'language=javascript']); */ -/** - * Flushes all data buffered since cork() was called. - * @method uncork - * @alias HttpDuplex.cork - * @memberof HttpDuplex - * @see cork - * @see {@link https://nodejs.org/api/stream.html#stream_writable_uncork|stream.Writeable.uncork} - */ - /** * Sends a chunk of the response body. This method may be called multiple times to provide successive parts of the * body. @@ -515,23 +547,6 @@

                                http-duplex.js

                                * {@link https://nodejs.org/api/http.html#http_event_checkcontinue|http.Server/checkContinue} */ -/** - * Sends a response header to the client request. Must only be called one time and before calling response.end(). - * @method writeHead - * @alias HttpDuplex.writeHead - * @memberof HttpDuplex - * @param {Number} statusCode 3-digit HTTP status code, like 404 - * @param {String} [statusMessage] An optional human readable status message to send with the status code - * @param {Object} [headers] An object containing the response headers to send - * @see {@link https://nodejs.org/api/http.html#http_response_writehead_statuscode_statusmessage_headers|response.writeHead} - * @example var content = 'Under Construction...'; - * response.writeHead(200, { - * 'Content-Length': Buffer.byteLength(content), - * 'Content-Type': 'text/plain' - * }); - * response.end(content); - */ - /** * __Warning:__ This has been deprecated in node, __don't__ use it. Any apis that require this funtion should be * updated to use writeHead insted. diff --git a/package.json b/package.json index 1673c4e..0b41629 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-git-server", - "version": "0.3.3", + "version": "0.3.4", "description": "🎡 A configurable git server written in Node.js", "author": "Gabriel J. Csapo ", "contributors": [ From 6d80414f3d8736cf7bfb3a5bd41001f73b649d09 Mon Sep 17 00:00:00 2001 From: Gabriel Csapo Date: Thu, 23 Nov 2017 20:39:38 -0800 Subject: [PATCH 04/26] updates docs page --- docs/index.html | 6 +----- package.json | 10 +++++----- tryitout.js | 4 +++- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/docs/index.html b/docs/index.html index 96ab97d..486061a 100644 --- a/docs/index.html +++ b/docs/index.html @@ -8,9 +8,5 @@
                                - + diff --git a/package.json b/package.json index 0b41629..0c6d386 100644 --- a/package.json +++ b/package.json @@ -26,18 +26,18 @@ "lint": "eslint .", "test": "tape test/*.js", "coverage": "tap test/*.js --coverage --coverage-report=lcov", - "generate-docs": "tryitout --template=landing --output=./docs && jsdoc -c jsdoc.json" + "generate-docs": "tryitout && jsdoc -c jsdoc.json" }, "dependencies": { "through": "^2.3.8" }, "devDependencies": { - "async": "^2.5.0", + "async": "^2.6.0", "docdash": "^0.4.0", - "eslint": "^4.10.0", + "eslint": "^4.11.0", "jsdoc": "^3.5.5", - "tap": "^10.7.2", + "tap": "^10.7.3", "tape": "^4.8.0", - "tryitout": "^1.0.0" + "tryitout": "^1.2.0" } } diff --git a/tryitout.js b/tryitout.js index 707a7f6..9a7d639 100644 --- a/tryitout.js +++ b/tryitout.js @@ -31,5 +31,7 @@ module.exports = { }, footer: `
                                Made with ☕️ by @gabrielcsapo
                                - ` + `, + template: 'landing', + output: './docs' }; From 0961dacd37ce8b4514a8f2bbcb6811b43379e8f3 Mon Sep 17 00:00:00 2001 From: Gabriel Csapo Date: Sun, 3 Dec 2017 17:41:52 -0800 Subject: [PATCH 05/26] makes authenticate more flexible (#30) * makes authenticate more flexible - [BREAKING] changes the interface for authentication to make it more flexible - when error is sent back to client ensure error is string * updates readme and example * adds README notice --- CHANGELOG.md | 5 + README.md | 16 +- docs/code/Git.html | 12 +- docs/code/HttpDuplex.html | 2 +- docs/code/Service.html | 2 +- docs/code/git.js.html | 36 ++-- docs/code/global.html | 294 +++++++++++++++++++++++++++++++++ docs/code/http-duplex.js.html | 2 +- docs/code/index.html | 14 +- docs/code/module-lib_util.html | 2 +- docs/code/service.js.html | 2 +- docs/code/util.js.html | 2 +- example/index.js | 25 ++- lib/git.js | 34 ++-- test/git.js | 32 +++- 15 files changed, 421 insertions(+), 59 deletions(-) create mode 100644 docs/code/global.html diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d8e1ef..95e0201 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# Unreleased + +- [BREAKING] changes the interface for authentication to make it more flexible +- when error is sent back to client ensure error is string + # 0.3.4 (11/10/2017) - updates duplex lib to fix cork, uncork and add some chaining diff --git a/README.md b/README.md index 3a86497..d65ba3a 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ > 🎡 A configurable git server written in Node.js +>> there be 🐲 here! The API's and functionality are still be cemented, anything before a 1.0.0 release will be subject to change. + [![Npm Version](https://img.shields.io/npm/v/node-git-server.svg)](https://www.npmjs.com/package/node-git-server) [![Build Status](https://travis-ci.org/gabrielcsapo/node-git-server.svg?branch=master)](https://travis-ci.org/gabrielcsapo/node-git-server) [![Coverage Status](https://lcov-server.gabrielcsapo.com/badge/github%2Ecom/gabrielcsapo/node-git-server.svg)](https://lcov-server.gabrielcsapo.com/coverage/github%2Ecom/gabrielcsapo/node-git-server) @@ -20,11 +22,17 @@ npm install node-git-server ```javascript const Server = require('node-git-server'); -const repo = new Server(path.resolve(__dirname, 'tmp'), { +const repos = new Server(path.resolve(__dirname, 'tmp'), { autoCreate: true, - authenticate: (type, repo, username, password, next) => { - console.log(type, repo, username, password); - next(); + authenticate: (type, repo, user, next) => { + if(type == 'upload') { + user((username, password) => { + console.log(username, password); + next(); + }); + } else { + next(); + } } }); const port = process.env.PORT || 7005; diff --git a/docs/code/Git.html b/docs/code/Git.html index 657d147..181a857 100644 --- a/docs/code/Git.html +++ b/docs/code/Git.html @@ -22,7 +22,7 @@
                                @@ -393,7 +393,7 @@

                                (static) close<
                                Source:
                                @@ -829,7 +829,7 @@

                                (static) handl
                                Source:
                                @@ -1118,7 +1118,7 @@

                                (static) liste
                                Source:
                                @@ -1882,7 +1882,7 @@

                                info

                                Source:
                                @@ -2082,7 +2082,7 @@

                                info

                                Source:
                                diff --git a/docs/code/HttpDuplex.html b/docs/code/HttpDuplex.html index 11cba24..0aa4144 100644 --- a/docs/code/HttpDuplex.html +++ b/docs/code/HttpDuplex.html @@ -22,7 +22,7 @@
                                diff --git a/docs/code/Service.html b/docs/code/Service.html index 66552ea..2c2603e 100644 --- a/docs/code/Service.html +++ b/docs/code/Service.html @@ -22,7 +22,7 @@
                                diff --git a/docs/code/git.js.html b/docs/code/git.js.html index c5abe24..aa515c1 100644 --- a/docs/code/git.js.html +++ b/docs/code/git.js.html @@ -22,7 +22,7 @@
                                @@ -275,6 +275,21 @@

                                git.js

                                }); } } + /** + * returns the typeof service being process + * @method getType + * @param {Service} service - the service type + * @return {String} - will respond with either upload or download + */ + getType(service) { + switch(service) { + case 'upload-pack': + case 'receive-pack': + return 'upload'; + default: + return 'download'; + } + } /** * Handle incoming HTTP requests with a connect-style middleware * @method handle @@ -314,7 +329,7 @@

                                git.js

                                res.setHeader("Content-Type", 'text/plain'); res.setHeader("WWW-Authenticate", 'Basic realm="authorization needed"'); res.writeHead(401); - res.end(error); + res.end(typeof error === 'string' ? error : error.toString()); return; } else { return infoResponse(self, repo, service, req, res); @@ -323,16 +338,15 @@

                                git.js

                                // check if the repo is authenticated if(this.authenticate) { - basicAuth(req, res, (username, password) => { - var promise = this.authenticate(service === 'upload-pack' ? 'upload' : 'download', repoName, username, password, (error) => { - return next(error); - }); - if(promise instanceof Promise) { - return promise - .then(next) - .catch(next); - } + const type = this.getType(service); + const promise = this.authenticate(type, repoName, basicAuth.bind(null, req, res), (error) => { + return next(error); }); + if(promise instanceof Promise) { + return promise + .then(next) + .catch(next); + } } else { return next(); } diff --git a/docs/code/global.html b/docs/code/global.html new file mode 100644 index 0000000..669d106 --- /dev/null +++ b/docs/code/global.html @@ -0,0 +1,294 @@ + + + + + Global - Documentation + + + + + + + + + + + + + + + + +
                                + +

                                Global

                                + + + + + + + +
                                + +
                                + +

                                + +

                                + + +
                                + +
                                +
                                + + + +
                                + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                                + + + + + + + + +
                                + + + + + + + + + + + + + + +

                                Methods

                                + + + + + + +

                                getType(service) → {String}

                                + + + + + + +
                                + + +
                                Source:
                                +
                                + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                                + + + + + +
                                +

                                returns the typeof service being process

                                +
                                + + + + + + + + + + + +
                                Parameters:
                                + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                                NameTypeDescription
                                service + + +Service + + + +

                                the service type

                                + + + + + + + + + + + + + + +
                                Returns:
                                + + +
                                +
                                  +
                                • will respond with either upload or download
                                • +
                                +
                                + + + +
                                +
                                + Type +
                                +
                                + +String + + +
                                +
                                + + + + + + + + + + +
                                + +
                                + + + + +
                                + +
                                + + + + + + + \ No newline at end of file diff --git a/docs/code/http-duplex.js.html b/docs/code/http-duplex.js.html index c571ed9..c6e7816 100644 --- a/docs/code/http-duplex.js.html +++ b/docs/code/http-duplex.js.html @@ -22,7 +22,7 @@

                                npm install node-git-server

                                Usage

                                const Server = require('node-git-server');
                                 const repo = new Server(path.resolve(__dirname, 'tmp'), {
                                     autoCreate: true,
                                -    authenticate: (type, repo, username, password, next) => {
                                -      console.log(type, repo, username, password);
                                -      next();
                                +    authenticate: (type, repo, user, next) => {
                                +      if(type == 'upload') {
                                +        user((username, password) => {
                                +          console.log(username, password);
                                +          next();
                                +        });
                                +      } else {
                                +        next();
                                +      }
                                     }
                                 });
                                 const port = process.env.PORT || 7005;
                                diff --git a/docs/code/module-lib_util.html b/docs/code/module-lib_util.html
                                index f97013f..19d87f7 100644
                                --- a/docs/code/module-lib_util.html
                                +++ b/docs/code/module-lib_util.html
                                @@ -22,7 +22,7 @@
                                 
                                 
                                 
                                 
                                 
                                diff --git a/docs/code/service.js.html b/docs/code/service.js.html index e293e33..f6073fd 100644 --- a/docs/code/service.js.html +++ b/docs/code/service.js.html @@ -22,7 +22,7 @@
                                diff --git a/docs/code/util.js.html b/docs/code/util.js.html index fe4bd43..458281a 100644 --- a/docs/code/util.js.html +++ b/docs/code/util.js.html @@ -22,7 +22,7 @@
                                diff --git a/example/index.js b/example/index.js index 38e3ec8..8b43258 100644 --- a/example/index.js +++ b/example/index.js @@ -4,32 +4,39 @@ const Server = require('../'); const port = process.env.PORT || 7005; -const git = new Server(path.normalize(path.resolve(__dirname, 'tmp')), { +const repos = new Server(path.normalize(path.resolve(__dirname, 'tmp')), { autoCreate: true, - authenticate: (type, repo, username, password, next) => { - console.log(type, repo, username, password); // eslint-disable-line - next(); + authenticate: (type, repo, user, next) => { + console.log(type, repo); // eslint-disable-line + if(type == 'upload') { + user((username, password) => { + console.log(username, password); // eslint-disable-line + next(); + }); + } else { + next(); + } } }); -git.on('push', (push) => { +repos.on('push', (push) => { console.log(`push ${push.repo} / ${push.commit} ( ${push.branch} )`); // eslint-disable-line - git.list((err, result) => { + repos.list((err, result) => { console.log(result); // eslint-disable-line }); push.accept(); }); -git.on('fetch', (fetch) => { +repos.on('fetch', (fetch) => { console.log('username', fetch.username); // eslint-disable-line console.log('fetch ' + fetch.repo + '/' + fetch.commit); // eslint-disable-line fetch.accept(); }); -git.listen(port, (error) => { +repos.listen(port, (error) => { if(error) return console.error(`failed to start git-server because of error ${error}`); // eslint-disable-line console.log(`node-git-server running at http://localhost:${port}`); // eslint-disable-line - git.list((err, result) => { + repos.list((err, result) => { if (!result) { console.log("No repositories available..."); // eslint-disable-line } else { diff --git a/lib/git.js b/lib/git.js index 7367544..a9939af 100644 --- a/lib/git.js +++ b/lib/git.js @@ -236,6 +236,21 @@ class Git extends EventEmitter { }); } } + /** + * returns the typeof service being process + * @method getType + * @param {Service} service - the service type + * @return {String} - will respond with either upload or download + */ + getType(service) { + switch(service) { + case 'upload-pack': + case 'receive-pack': + return 'upload'; + default: + return 'download'; + } + } /** * Handle incoming HTTP requests with a connect-style middleware * @method handle @@ -275,7 +290,7 @@ class Git extends EventEmitter { res.setHeader("Content-Type", 'text/plain'); res.setHeader("WWW-Authenticate", 'Basic realm="authorization needed"'); res.writeHead(401); - res.end(error); + res.end(typeof error === 'string' ? error : error.toString()); return; } else { return infoResponse(self, repo, service, req, res); @@ -284,16 +299,15 @@ class Git extends EventEmitter { // check if the repo is authenticated if(this.authenticate) { - basicAuth(req, res, (username, password) => { - var promise = this.authenticate(service === 'upload-pack' ? 'upload' : 'download', repoName, username, password, (error) => { - return next(error); - }); - if(promise instanceof Promise) { - return promise - .then(next) - .catch(next); - } + const type = this.getType(service); + const promise = this.authenticate(type, repoName, basicAuth.bind(null, req, res), (error) => { + return next(error); }); + if(promise instanceof Promise) { + return promise + .then(next) + .catch(next); + } } else { return next(); } diff --git a/test/git.js b/test/git.js index 0dcb424..7294834 100644 --- a/test/git.js +++ b/test/git.js @@ -548,13 +548,13 @@ test('git', (t) => { }); }, (callback) => { - const glog = _spawn('git', [ 'log', '--all'], { cwd : repoDir + '/doom.git' }); + const glog = _spawn('git', [ 'log'], { cwd : repoDir + '/doom.git' }); glog.on('exit', (code) => { t.equal(code, 128); callback(); }); var data = ''; - glog.stderr.on('data', (buf) => data += buf ); + glog.stderr.on('data', (buf) => data += buf); glog.stderr.on('end', () => { const res = /fatal: bad default revision 'HEAD'/.test(data) || /fatal: your current branch 'master' does not have any commits yet/.test(data); t.ok(res); @@ -621,9 +621,16 @@ test('git', (t) => { const repos = new GitServer(repoDir, { autoCreate: true, - authenticate: (type, repo, username, password, next) => { - if(type == 'download', repo == 'doom' && username == 'root' && password == 'root') { - next(); + authenticate: (type, repo, user, next) => { + + if(type == 'download', repo == 'doom') { + user((username, password) => { + if(username == 'root' && password == 'root') { + next(); + } else { + next('that is not the correct password'); + } + }); } else { next('that is not the correct password'); } @@ -677,12 +684,19 @@ test('git', (t) => { const repos = new GitServer(repoDir, { autoCreate: true, - authenticate: (type, repo, username, password) => { + authenticate: (type, repo, user) => { return new Promise(function(resolve, reject) { - if(type == 'download', repo == 'doom' && username == 'root' && password == 'root') { - return resolve(); + if(type == 'download', repo == 'doom') { + user((username, password) => { + if(username == 'root' && password == 'root') { + return resolve(); + } else { + return reject('that is not the correct password'); + } + }); + } else { + return reject('that is not the correct password'); } - return reject('that is not the correct password'); }); } }); From abd8fa4a72b69dc6ecdbe617355155809328d008 Mon Sep 17 00:00:00 2001 From: Gabriel Csapo Date: Sun, 3 Dec 2017 17:43:29 -0800 Subject: [PATCH 06/26] 0.4.0 - [BREAKING] changes the interface for authentication to make it more flexible - when error is sent back to client ensure error is string --- CHANGELOG.md | 2 +- docs/code/index.html | 5 ++++- package.json | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95e0201..165455e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# Unreleased +# 0.4.0 (12/03/2017) - [BREAKING] changes the interface for authentication to make it more flexible - when error is sent back to client ensure error is string diff --git a/docs/code/index.html b/docs/code/index.html index 50c9e5e..34fb666 100644 --- a/docs/code/index.html +++ b/docs/code/index.html @@ -48,6 +48,9 @@

                                Home

                                Classes

                                • node-git-server

                                  🎡 A configurable git server written in Node.js

                                  +
                                  +

                                  there be 🐲 here! The API's and functionality are still be cemented, anything before a 1.0.0 release will be subject to change.

                                  +

                                  Npm Version Build Status @@ -57,7 +60,7 @@

                                  Home

                                  Classes

                                  • npm npm

                                    Install

                                    npm install node-git-server

                                    Usage

                                    const Server = require('node-git-server');
                                    -const repo = new Server(path.resolve(__dirname, 'tmp'), {
                                    +const repos = new Server(path.resolve(__dirname, 'tmp'), {
                                         autoCreate: true,
                                         authenticate: (type, repo, user, next) => {
                                           if(type == 'upload') {
                                    diff --git a/package.json b/package.json
                                    index 0c6d386..b6a80b1 100644
                                    --- a/package.json
                                    +++ b/package.json
                                    @@ -1,6 +1,6 @@
                                     {
                                       "name": "node-git-server",
                                    -  "version": "0.3.4",
                                    +  "version": "0.4.0",
                                       "description": "🎡 A configurable git server written in Node.js",
                                       "author": "Gabriel J. Csapo ",
                                       "contributors": [
                                    
                                    From f3f73967b2f22b4956bc5a6d1f987b65eed3b91a Mon Sep 17 00:00:00 2001
                                    From: Gabriel Csapo 
                                    Date: Mon, 4 Dec 2017 23:36:54 -0800
                                    Subject: [PATCH 07/26] fixes type to be the same as the event names (#32)
                                    
                                    * fixes type to be the same as the event names
                                    
                                    * updates changelog
                                    ---
                                     CHANGELOG.md     | 4 ++++
                                     README.md        | 4 ++--
                                     example/index.js | 2 +-
                                     lib/git.js       | 7 ++++---
                                     package.json     | 2 +-
                                     5 files changed, 12 insertions(+), 7 deletions(-)
                                    
                                    diff --git a/CHANGELOG.md b/CHANGELOG.md
                                    index 165455e..55cd4b0 100644
                                    --- a/CHANGELOG.md
                                    +++ b/CHANGELOG.md
                                    @@ -1,3 +1,7 @@
                                    +# 0.4.1 (12/04/2017)
                                    +
                                    +- fixes type to be the same as the event names
                                    +
                                     # 0.4.0 (12/03/2017)
                                     
                                     - [BREAKING] changes the interface for authentication to make it more flexible
                                    diff --git a/README.md b/README.md
                                    index d65ba3a..35435ae 100644
                                    --- a/README.md
                                    +++ b/README.md
                                    @@ -2,7 +2,7 @@
                                     
                                     > 🎡 A configurable git server written in Node.js
                                     
                                    ->> there be 🐲 here! The API's and functionality are still be cemented, anything before a 1.0.0 release will be subject to change. 
                                    +>> there be 🐲 here! The API's and functionality are still be cemented, anything before a 1.0.0 release will be subject to change.
                                     
                                     [![Npm Version](https://img.shields.io/npm/v/node-git-server.svg)](https://www.npmjs.com/package/node-git-server)
                                     [![Build Status](https://travis-ci.org/gabrielcsapo/node-git-server.svg?branch=master)](https://travis-ci.org/gabrielcsapo/node-git-server)
                                    @@ -25,7 +25,7 @@ const Server = require('node-git-server');
                                     const repos = new Server(path.resolve(__dirname, 'tmp'), {
                                         autoCreate: true,
                                         authenticate: (type, repo, user, next) => {
                                    -      if(type == 'upload') {
                                    +      if(type == 'push') {
                                             user((username, password) => {
                                               console.log(username, password);
                                               next();
                                    diff --git a/example/index.js b/example/index.js
                                    index 8b43258..1245abf 100644
                                    --- a/example/index.js
                                    +++ b/example/index.js
                                    @@ -8,7 +8,7 @@ const repos = new Server(path.normalize(path.resolve(__dirname, 'tmp')), {
                                         autoCreate: true,
                                         authenticate: (type, repo, user, next) => {
                                           console.log(type, repo); // eslint-disable-line
                                    -      if(type == 'upload') {
                                    +      if(type == 'push') {
                                             user((username, password) => {
                                               console.log(username, password); // eslint-disable-line
                                               next();
                                    diff --git a/lib/git.js b/lib/git.js
                                    index a9939af..d7b4f39 100644
                                    --- a/lib/git.js
                                    +++ b/lib/git.js
                                    @@ -239,16 +239,17 @@ class Git extends EventEmitter {
                                       /**
                                        * returns the typeof service being process
                                        * @method getType
                                    -   * @param  {Service} service - the service type
                                    +   * @param  {String} service - the service type
                                        * @return {String}  - will respond with either upload or download
                                        */
                                       getType(service) {
                                         switch(service) {
                                           case 'upload-pack':
                                    +        return 'fetch';
                                           case 'receive-pack':
                                    -        return 'upload';
                                    +        return 'push';
                                           default:
                                    -        return 'download';
                                    +        return 'unknown';
                                         }
                                       }
                                       /**
                                    diff --git a/package.json b/package.json
                                    index b6a80b1..60ea09e 100644
                                    --- a/package.json
                                    +++ b/package.json
                                    @@ -1,6 +1,6 @@
                                     {
                                       "name": "node-git-server",
                                    -  "version": "0.4.0",
                                    +  "version": "0.4.1",
                                       "description": "🎡 A configurable git server written in Node.js",
                                       "author": "Gabriel J. Csapo ",
                                       "contributors": [
                                    
                                    From 9aec84c8d2f8a472e53fa51c0fd191c10421062a Mon Sep 17 00:00:00 2001
                                    From: Gabriel Csapo 
                                    Date: Thu, 7 Dec 2017 22:09:47 -0800
                                    Subject: [PATCH 08/26] adds https support (#35)
                                    
                                    * adds https support
                                    
                                    * ensures options is set
                                    
                                    * updates readme and adds example for https
                                    
                                    * fixes lint
                                    
                                    * documents git ssl override
                                    ---
                                     CHANGELOG.md            |  4 ++++
                                     README.md               | 24 ++++++++++++++++++++----
                                     example/certificate.pem | 12 ++++++++++++
                                     example/index.js        | 30 ++++++++++++++++++++++++++----
                                     example/privatekey.pem  | 15 +++++++++++++++
                                     lib/git.js              | 18 +++++++++++++++---
                                     6 files changed, 92 insertions(+), 11 deletions(-)
                                     create mode 100644 example/certificate.pem
                                     create mode 100644 example/privatekey.pem
                                    
                                    diff --git a/CHANGELOG.md b/CHANGELOG.md
                                    index 55cd4b0..4922f36 100644
                                    --- a/CHANGELOG.md
                                    +++ b/CHANGELOG.md
                                    @@ -1,3 +1,7 @@
                                    +# Unreleased
                                    +
                                    +- adds https support
                                    +
                                     # 0.4.1 (12/04/2017)
                                     
                                     - fixes type to be the same as the event names
                                    diff --git a/README.md b/README.md
                                    index 35435ae..9cbec6a 100644
                                    --- a/README.md
                                    +++ b/README.md
                                    @@ -21,7 +21,9 @@ npm install node-git-server
                                     # Usage
                                     
                                     ```javascript
                                    +const path = require('path');
                                     const Server = require('node-git-server');
                                    +
                                     const repos = new Server(path.resolve(__dirname, 'tmp'), {
                                         autoCreate: true,
                                         authenticate: (type, repo, user, next) => {
                                    @@ -38,14 +40,12 @@ const repos = new Server(path.resolve(__dirname, 'tmp'), {
                                     const port = process.env.PORT || 7005;
                                     
                                     repos.on('push', (push) => {
                                    -    console.log('push ' + push.repo + '/' + push.commit
                                    -        + ' (' + push.branch + ')'
                                    -    );
                                    +    console.log(`push ${push.repo}/${push.commit} (${push.branch})`);
                                         push.accept();
                                     });
                                     
                                     repos.on('fetch', (fetch) => {
                                    -    console.log('fetch ' + fetch.commit);
                                    +    console.log(`fetch ${fetch.commit}`);
                                         fetch.accept();
                                     });
                                     
                                    @@ -73,6 +73,22 @@ To http://localhost:7005/beep
                                      * [new branch]      master -> master
                                     ```
                                     
                                    +## Example
                                    +
                                    +Running the following command will start up a simple http server:
                                    +
                                    +```
                                    +node example/index.js
                                    +```
                                    +
                                    +If you want to try using https run the following
                                    +
                                    +```
                                    +node example/index.js --https
                                    +```
                                    +
                                    +> When running https with self-signed certs there are two ways to override the git-clients behavior using `git config http.sslVerify false` or `git config --global http.sslCAInfo /path/to/cert.pem`
                                    +
                                     For more information please visit the [docs](http://www.gabrielcsapo.com/node-git-server/code/index.html)
                                     
                                     # Philosophy   
                                    diff --git a/example/certificate.pem b/example/certificate.pem
                                    new file mode 100644
                                    index 0000000..da8d389
                                    --- /dev/null
                                    +++ b/example/certificate.pem
                                    @@ -0,0 +1,12 @@
                                    +-----BEGIN CERTIFICATE-----
                                    +MIIB0TCCAToCCQCvzaaNTZEFTDANBgkqhkiG9w0BAQUFADAtMQswCQYDVQQGEwJV
                                    +UzELMAkGA1UECAwCQ0ExETAPBgNVBAcMCFNhbiBKb3NlMB4XDTE3MTIwNzE5MTI0
                                    +NloXDTE4MDEwNjE5MTI0NlowLTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMREw
                                    +DwYDVQQHDAhTYW4gSm9zZTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAplVm
                                    +UrhFbgb+g7gHDjfdTZUwqNnJ6AajytmqmzV7ceTeXKtKfNAN7i2DyFxf/IwQWoxs
                                    +0g7MOE8UP19GCInK0I5T9svvbtSVEfxrtGLMBY7qnojN7Q+DIjdvM7f/m5W8oNkQ
                                    +qb3EzkBKX6A5HobxKqTdu40IBLBkxa0faEmU84MCAwEAATANBgkqhkiG9w0BAQUF
                                    +AAOBgQAzN5VrWE0bFT/UZgUCHrR6h+EQRfGCybAq6MmS1hEoHkLyLPIjokcRWxFe
                                    +eufVyxesJaUiO6f3W34J2NoZK4NsEF931t/n12bB8Ku8H1tokNsyWqJns2Whj+bR
                                    +012osVh6ghdOvKD0yrnv6oLjMU51A8UKuxh51xoMThU0vBwvDg==
                                    +-----END CERTIFICATE-----
                                    diff --git a/example/index.js b/example/index.js
                                    index 1245abf..340f3f1 100644
                                    --- a/example/index.js
                                    +++ b/example/index.js
                                    @@ -1,3 +1,21 @@
                                    +// You Can Use The Commands Below To Generate A Self Signed Certificate For Use With This Tutorial
                                    +// These Commands Require That You have 'openssl' installed on your system
                                    +// openssl genrsa -out privatekey.pem 1024
                                    +// openssl req -new -key privatekey.pem -out certrequest.csr
                                    +// openssl x509 -req -in certrequest.csr -signkey privatekey.pem -out certificate.pem
                                    +
                                    +let type = 'http';
                                    +
                                    +process.argv.slice(2).forEach((arg) => {
                                    +  switch(arg) {
                                    +    case 'https':
                                    +    case '--https':
                                    +      type = 'https';
                                    +    break;
                                    +  }
                                    +});
                                    +
                                    +const fs = require('fs');
                                     const path = require('path');
                                     
                                     const Server = require('../');
                                    @@ -28,14 +46,18 @@ repos.on('push', (push) => {
                                     });
                                     
                                     repos.on('fetch', (fetch) => {
                                    -    console.log('username', fetch.username); // eslint-disable-line
                                    -    console.log('fetch ' + fetch.repo + '/' + fetch.commit); // eslint-disable-line
                                    +    console.log(`username ${fetch.username}`); // eslint-disable-line
                                    +    console.log(`fetch ${fetch.repo}/${fetch.commit}`); // eslint-disable-line
                                         fetch.accept();
                                     });
                                     
                                    -repos.listen(port, (error) => {
                                    +repos.listen(port, {
                                    +  type,
                                    +  key: fs.readFileSync('./privatekey.pem'),
                                    +  cert: fs.readFileSync('./certificate.pem')
                                    +}, (error) => {
                                         if(error) return console.error(`failed to start git-server because of error ${error}`); // eslint-disable-line
                                    -    console.log(`node-git-server running at http://localhost:${port}`); // eslint-disable-line
                                    +    console.log(`node-git-server running at ${type}://localhost:${port}`); // eslint-disable-line
                                         repos.list((err, result) => {
                                             if (!result) {
                                                 console.log("No repositories available..."); // eslint-disable-line
                                    diff --git a/example/privatekey.pem b/example/privatekey.pem
                                    new file mode 100644
                                    index 0000000..eff8a45
                                    --- /dev/null
                                    +++ b/example/privatekey.pem
                                    @@ -0,0 +1,15 @@
                                    +-----BEGIN RSA PRIVATE KEY-----
                                    +MIICWwIBAAKBgQCmVWZSuEVuBv6DuAcON91NlTCo2cnoBqPK2aqbNXtx5N5cq0p8
                                    +0A3uLYPIXF/8jBBajGzSDsw4TxQ/X0YIicrQjlP2y+9u1JUR/Gu0YswFjuqeiM3t
                                    +D4MiN28zt/+blbyg2RCpvcTOQEpfoDkehvEqpN27jQgEsGTFrR9oSZTzgwIDAQAB
                                    +AoGAb3gr6qOzY9ksF/nsQIsPtD6XLZFGzkgk3Hyi6QEeiWVn35KriJmlvEikWFIP
                                    +wZ/cFdKl2uAv3EyitRWUSYSOdcD+tri253WkwpKr8qEq3MKdEsQGZlPiO2MJpWsa
                                    +4vsy1bleUxqbB2TYIIXdjgD8TpTCh8sc8pVxEWEuEThxIIECQQDY9TSZkZHqsqpC
                                    +bK/VUpluj3coZJcczk64ZAdkWYlXfNpvgdT3ViPjpdEYeabzTP0xI7OHkx6fVLiS
                                    +87Y6EwWfAkEAxEQKhgj+saudBzl2EUQW5qntdJh3Fr+OLErSHb4M5E/nWvxW6il0
                                    +XWRE3b7KrikThPkwUtXPTBNGiZ7D1dvfnQJAHMLk5jbWETb+OzANX0pD7NQ4B7LO
                                    +FZOD/A3GrRbxjhePHZkokmFpAJTK02PNLhPWvNzuv9pRBO5GSbTlQ22iIQJAQiEy
                                    +8oqhVrgeRsrjr1mj5cCn07tzlOSiQOZM+dyJd3w81flkR64EGVupoJWisSACBbH4
                                    +yFBmcpmkEMa/8ZUOOQJAfAqyF5aCRh4MbPT7pCGSf0gckc3p6qISb1Zodh7GLSdb
                                    +f/VvQzhRG3MKnSl+ZQ+GQT+F+FeZl0/ZPjVRYG/Avw==
                                    +-----END RSA PRIVATE KEY-----
                                    diff --git a/lib/git.js b/lib/git.js
                                    index d7b4f39..fc91f6e 100644
                                    --- a/lib/git.js
                                    +++ b/lib/git.js
                                    @@ -1,6 +1,7 @@
                                     const fs = require('fs');
                                     const path = require('path');
                                     const http = require('http');
                                    +const https = require('https');
                                     const url = require('url');
                                     const qs = require('querystring');
                                     const httpDuplex = require('./http-duplex');
                                    @@ -427,13 +428,24 @@ class Git extends EventEmitter {
                                        * @method listen
                                        * @memberof Git
                                        * @param  {Number}   port     - the port to start the server on
                                    +   * @param  {Object=}   options  - the options to add extended functionality to the server
                                    +   * @param  {String=}   options.type - this is either https or http (the default is http)
                                    +   * @param  {Buffer|String=}   options.key - the key file for the https server
                                    +   * @param  {Buffer|String=}   options.cert - the cert file for the https server
                                        * @param  {Function} callback - the function to call when server is started or error has occured
                                        */
                                    -  listen(port, callback) {
                                    -      var self = this;
                                    -      this.server = http.createServer(function(req, res) {
                                    +  listen(port, options, callback) {
                                    +      const self = this;
                                    +      if(typeof options == 'function' || !options) {
                                    +        callback = options;
                                    +        options = { type: 'http' };
                                    +      }
                                    +      const createServer = options.type == 'http' ? http.createServer : https.createServer.bind(this, options);
                                    +
                                    +      this.server = createServer(function(req, res) {
                                               self.handle(req, res);
                                           });
                                    +
                                           this.server.listen(port, callback);
                                       }
                                       /**
                                    
                                    From 2d9d4c809ca676b79b67be7d740437d637e0b0b9 Mon Sep 17 00:00:00 2001
                                    From: Gabriel Csapo 
                                    Date: Thu, 7 Dec 2017 22:11:02 -0800
                                    Subject: [PATCH 09/26] 0.4.2 - adds https support
                                    
                                    ---
                                     CHANGELOG.md          |   2 +-
                                     docs/code/Git.html    | 210 +++++++++++++++++++++++++++++++++++++++---
                                     docs/code/git.js.html |  25 +++--
                                     docs/code/global.html |   4 +-
                                     docs/code/index.html  |  21 +++--
                                     package.json          |   2 +-
                                     6 files changed, 231 insertions(+), 33 deletions(-)
                                    
                                    diff --git a/CHANGELOG.md b/CHANGELOG.md
                                    index 4922f36..4bf0891 100644
                                    --- a/CHANGELOG.md
                                    +++ b/CHANGELOG.md
                                    @@ -1,4 +1,4 @@
                                    -# Unreleased
                                    +# 0.4.2 (12/07/2017)
                                     
                                     - adds https support
                                     
                                    diff --git a/docs/code/Git.html b/docs/code/Git.html
                                    index 181a857..3e058ee 100644
                                    --- a/docs/code/Git.html
                                    +++ b/docs/code/Git.html
                                    @@ -65,7 +65,7 @@ 

                                    new GitSource:
                                    @@ -393,7 +393,7 @@

                                    (static) close<
                                    Source:
                                    @@ -477,7 +477,7 @@

                                    (static) creat
                                    Source:
                                    @@ -653,7 +653,7 @@

                                    (static) exist
                                    Source:
                                    @@ -829,7 +829,7 @@

                                    (static) handl
                                    Source:
                                    @@ -985,7 +985,7 @@

                                    (static) listSource:
                                    @@ -1106,7 +1106,7 @@
                                    Parameters:
                                    -

                                    (static) listen(port, callback)

                                    +

                                    (static) listen(port, optionsopt, callback)

                                    @@ -1118,7 +1118,7 @@

                                    (static) liste
                                    Source:
                                    @@ -1184,6 +1184,8 @@

                                    Parameters:
                                    Type + Attributes + @@ -1209,6 +1211,14 @@
                                    Parameters:
                                    + + + + + + + + @@ -1217,6 +1227,168 @@
                                    Parameters:
                                    + + + options + + + + + +Object + + + + + + + + + <optional>
                                    + + + + + + + + + + +

                                    the options to add extended functionality to the server

                                    +
                                    Properties
                                    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                                    NameTypeAttributesDescription
                                    type + + +String + + + + + + <optional>
                                    + + + + + +

                                    this is either https or http (the default is http)

                                    key + + +Buffer +| + +String + + + + + + + + + +

                                    the key file for the https server

                                    cert + + +Buffer +| + +String + + + + + + + + + +

                                    the cert file for the https server

                                    + + + + + + callback @@ -1232,6 +1404,14 @@
                                    Parameters:
                                    + + + + + + + + @@ -1274,7 +1454,7 @@

                                    (static) mkdir<
                                    Source:
                                    @@ -1458,7 +1638,7 @@

                                    fetch

                                    Source:
                                    @@ -1682,7 +1862,7 @@

                                    head

                                    Source:
                                    @@ -1882,7 +2062,7 @@

                                    info

                                    Source:
                                    @@ -2082,7 +2262,7 @@

                                    info

                                    Source:
                                    @@ -2282,7 +2462,7 @@

                                    push

                                    Source:
                                    @@ -2528,7 +2708,7 @@

                                    tag

                                    Source:
                                    diff --git a/docs/code/git.js.html b/docs/code/git.js.html index aa515c1..8435a5f 100644 --- a/docs/code/git.js.html +++ b/docs/code/git.js.html @@ -40,6 +40,7 @@

                                    git.js

                                    const fs = require('fs');
                                     const path = require('path');
                                     const http = require('http');
                                    +const https = require('https');
                                     const url = require('url');
                                     const qs = require('querystring');
                                     const httpDuplex = require('./http-duplex');
                                    @@ -278,16 +279,17 @@ 

                                    git.js

                                    /** * returns the typeof service being process * @method getType - * @param {Service} service - the service type + * @param {String} service - the service type * @return {String} - will respond with either upload or download */ getType(service) { switch(service) { case 'upload-pack': + return 'fetch'; case 'receive-pack': - return 'upload'; + return 'push'; default: - return 'download'; + return 'unknown'; } } /** @@ -465,13 +467,24 @@

                                    git.js

                                    * @method listen * @memberof Git * @param {Number} port - the port to start the server on + * @param {Object=} options - the options to add extended functionality to the server + * @param {String=} options.type - this is either https or http (the default is http) + * @param {Buffer|String=} options.key - the key file for the https server + * @param {Buffer|String=} options.cert - the cert file for the https server * @param {Function} callback - the function to call when server is started or error has occured */ - listen(port, callback) { - var self = this; - this.server = http.createServer(function(req, res) { + listen(port, options, callback) { + const self = this; + if(typeof options == 'function' || !options) { + callback = options; + options = { type: 'http' }; + } + const createServer = options.type == 'http' ? http.createServer : https.createServer.bind(this, options); + + this.server = createServer(function(req, res) { self.handle(req, res); }); + this.server.listen(port, callback); } /** diff --git a/docs/code/global.html b/docs/code/global.html index 669d106..cd62fad 100644 --- a/docs/code/global.html +++ b/docs/code/global.html @@ -127,7 +127,7 @@

                                    getTypeSource:
                                    @@ -211,7 +211,7 @@
                                    Parameters:
                                    -Service +String diff --git a/docs/code/index.html b/docs/code/index.html index 34fb666..d1181b8 100644 --- a/docs/code/index.html +++ b/docs/code/index.html @@ -49,7 +49,7 @@

                                    Home

                                    Classes

                                    • node-git-server

                                      🎡 A configurable git server written in Node.js

                                      -

                                      there be 🐲 here! The API's and functionality are still be cemented, anything before a 1.0.0 release will be subject to change.

                                      +

                                      there be 🐲 here! The API's and functionality are still be cemented, anything before a 1.0.0 release will be subject to change.

                                      Npm Version @@ -59,11 +59,13 @@

                                      Home

                                      Classes

                                      • devDependency Status npm npm

                                        -

                                        Install

                                        npm install node-git-server

                                        Usage

                                        const Server = require('node-git-server');
                                        +

                                        Install

                                        npm install node-git-server

                                        Usage

                                        const path = require('path');
                                        +const Server = require('node-git-server');
                                        +
                                         const repos = new Server(path.resolve(__dirname, 'tmp'), {
                                             autoCreate: true,
                                             authenticate: (type, repo, user, next) => {
                                        -      if(type == 'upload') {
                                        +      if(type == 'push') {
                                                 user((username, password) => {
                                                   console.log(username, password);
                                                   next();
                                        @@ -76,14 +78,12 @@ 

                                        Install

                                        npm install node-git-serve
                                         const port = process.env.PORT || 7005;
                                         
                                         repos.on('push', (push) => {
                                        -    console.log('push ' + push.repo + '/' + push.commit
                                        -        + ' (' + push.branch + ')'
                                        -    );
                                        +    console.log(`push ${push.repo}/${push.commit} (${push.branch})`);
                                             push.accept();
                                         });
                                         
                                         repos.on('fetch', (fetch) => {
                                        -    console.log('fetch ' + fetch.commit);
                                        +    console.log(`fetch ${fetch.commit}`);
                                             fetch.accept();
                                         });
                                         
                                        @@ -98,7 +98,12 @@ 

                                        Install

                                        npm install node-git-serve
                                         Writing objects: 100% (356/356), 46.20 KiB, done.
                                         Total 356 (delta 210), reused 355 (delta 210)
                                         To http://localhost:7005/beep
                                        - * [new branch]      master -> master

                                        For more information please visit the docs

                                        + * [new branch] master -> master

                                        Example

                                        Running the following command will start up a simple http server:

                                        +
                                        node example/index.js

                                        If you want to try using https run the following

                                        +
                                        node example/index.js --https
                                        +

                                        When running https with self-signed certs there are two ways to override the git-clients behavior using git config http.sslVerify false or git config --global http.sslCAInfo /path/to/cert.pem

                                        +
                                        +

                                        For more information please visit the docs

                                        Philosophy

                                        This library is aimed to have a zero dependency footprint. If you are reading this and you see dependencies, help to remove them 🐒.

                                        Thanks

                                        This is a hard fork from pushover.

                                  diff --git a/package.json b/package.json index 60ea09e..30e55e0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-git-server", - "version": "0.4.1", + "version": "0.4.2", "description": "🎡 A configurable git server written in Node.js", "author": "Gabriel J. Csapo ", "contributors": [ From 415e23d4f530e8d4bd22314e2f7917f9a8a7a01a Mon Sep 17 00:00:00 2001 From: Gabriel Csapo Date: Fri, 12 Jan 2018 23:02:32 -0800 Subject: [PATCH 10/26] updates tryitout and docs --- tryitout.js => .tryitout | 9 +- docs/index.html | 4241 +++++++++++++++++++++++++++++++++++++- docs/krayon.css | 18 + docs/krayon.min.js | 1 + docs/main.js | 5 + package.json | 6 +- 6 files changed, 4273 insertions(+), 7 deletions(-) rename tryitout.js => .tryitout (84%) create mode 100644 docs/krayon.css create mode 100644 docs/krayon.min.js create mode 100644 docs/main.js diff --git a/tryitout.js b/.tryitout similarity index 84% rename from tryitout.js rename to .tryitout index 9a7d639..cf355f4 100644 --- a/tryitout.js +++ b/.tryitout @@ -9,7 +9,7 @@ module.exports = { body: `

                                  ${description}

                                  -
                                  +        
                                         const Server = require('node-git-server');
                                         const repo = new Server(path.resolve(__dirname, 'tmp'), {
                                             autoCreate: true,
                                  @@ -33,5 +33,10 @@ module.exports = {
                                          
                                  Made with ☕️ by @gabrielcsapo
                                  `, template: 'landing', - output: './docs' + output: './docs', + externals: [ + "./docs/krayon.css", + "./docs/krayon.min.js", + "./docs/main.js" + ] }; diff --git a/docs/index.html b/docs/index.html index 486061a..be82179 100644 --- a/docs/index.html +++ b/docs/index.html @@ -5,8 +5,4245 @@ - +
                                  - + diff --git a/docs/krayon.css b/docs/krayon.css new file mode 100644 index 0000000..9c4af13 --- /dev/null +++ b/docs/krayon.css @@ -0,0 +1,18 @@ +.string { + color: #032f62 +} +.keyword { + color: #d73a49 +} +.operator { + color: #d73a49 +} +.function { + color: #005cc5 +} +.class { + color: #6f42c1 +} +.comment { + color: #6a737d +} diff --git a/docs/krayon.min.js b/docs/krayon.min.js new file mode 100644 index 0000000..0fa690d --- /dev/null +++ b/docs/krayon.min.js @@ -0,0 +1 @@ +require=function b(c,d,e){function a(h,i){if(!d[h]){if(!c[h]){var j="function"==typeof require&&require;if(!i&&j)return j(h,!0);if(g)return g(h,!0);var k=new Error("Cannot find module '"+h+"'");throw k.code="MODULE_NOT_FOUND",k}var f=d[h]={exports:{}};c[h][0].call(f.exports,function(b){var d=c[h][1][b];return a(d?d:b)},f,f.exports,b,c,d,e)}return d[h].exports}for(var g="function"==typeof require&&require,f=0;f127127{a=a.replace(e[b],(a,d)=>{let e=a.replace(d,"");return`{#${b}#${c(d)}#}${e}`})}),a.replace(/\{#([a-z]+)#(.*?)#\}/g,(a,b,c)=>""+d(c)+"")}},{"./util.js":1}]},{},[]); \ No newline at end of file diff --git a/docs/main.js b/docs/main.js new file mode 100644 index 0000000..f28f335 --- /dev/null +++ b/docs/main.js @@ -0,0 +1,5 @@ +window.onload = function() { + var Krayon = require('krayon'); + + document.querySelector('#code').innerHTML = Krayon(document.querySelector('#code').innerHTML); +} diff --git a/package.json b/package.json index 30e55e0..5263a6b 100644 --- a/package.json +++ b/package.json @@ -34,10 +34,10 @@ "devDependencies": { "async": "^2.6.0", "docdash": "^0.4.0", - "eslint": "^4.11.0", + "eslint": "^4.15.0", "jsdoc": "^3.5.5", - "tap": "^10.7.3", + "tap": "^11.0.1", "tape": "^4.8.0", - "tryitout": "^1.2.0" + "tryitout": "^2.0.6" } } From 82028631236f3e7cfda80f76e8f5e530aee0a10c Mon Sep 17 00:00:00 2001 From: hasezoey <10911626+hasezoey@users.noreply.github.com> Date: Tue, 29 May 2018 23:44:08 +0200 Subject: [PATCH 11/26] Fix for #38 (#39) * gitignore update for visual studio, Buffer Fix #38 * Update util.js * Update service.js --- .gitignore | 1 + lib/service.js | 4 ++-- lib/util.js | 2 +- test/fixtures/server/tmp/test.git/hooks/applypatch-msg.sample | 0 test/fixtures/server/tmp/test.git/hooks/commit-msg.sample | 0 test/fixtures/server/tmp/test.git/hooks/post-update.sample | 0 test/fixtures/server/tmp/test.git/hooks/pre-applypatch.sample | 0 test/fixtures/server/tmp/test.git/hooks/pre-commit.sample | 0 test/fixtures/server/tmp/test.git/hooks/pre-push.sample | 0 test/fixtures/server/tmp/test.git/hooks/pre-rebase.sample | 0 .../server/tmp/test.git/hooks/prepare-commit-msg.sample | 0 test/fixtures/server/tmp/test.git/hooks/update.sample | 0 12 files changed, 4 insertions(+), 3 deletions(-) mode change 100755 => 100644 test/fixtures/server/tmp/test.git/hooks/applypatch-msg.sample mode change 100755 => 100644 test/fixtures/server/tmp/test.git/hooks/commit-msg.sample mode change 100755 => 100644 test/fixtures/server/tmp/test.git/hooks/post-update.sample mode change 100755 => 100644 test/fixtures/server/tmp/test.git/hooks/pre-applypatch.sample mode change 100755 => 100644 test/fixtures/server/tmp/test.git/hooks/pre-commit.sample mode change 100755 => 100644 test/fixtures/server/tmp/test.git/hooks/pre-push.sample mode change 100755 => 100644 test/fixtures/server/tmp/test.git/hooks/pre-rebase.sample mode change 100755 => 100644 test/fixtures/server/tmp/test.git/hooks/prepare-commit-msg.sample mode change 100755 => 100644 test/fixtures/server/tmp/test.git/hooks/update.sample diff --git a/.gitignore b/.gitignore index c22ee1e..4fd2111 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ coverage example/test example/tmp package-lock.json +.vs/ \ No newline at end of file diff --git a/lib/service.js b/lib/service.js index 0f50176..4af2f20 100644 --- a/lib/service.js +++ b/lib/service.js @@ -50,7 +50,7 @@ class Service extends HttpDuplex { if(req.headers["authorization"]) { const tokens = req.headers["authorization"].split(" "); if (tokens[0] === "Basic") { - const splitHash = new Buffer(tokens[1], 'base64').toString('utf8').split(":"); + const splitHash = new Buffer.from(tokens[1], 'base64').toString('utf8').split(":"); this.username = splitHash.shift(); } } @@ -116,7 +116,7 @@ class Service extends HttpDuplex { self.emit('response', respStream, function endResponse() { - res.queue(new Buffer('0000')); + res.queue(new Buffer.from('0000')); res.queue(null); }); ps.stdout.pipe(respStream).pipe(res); diff --git a/lib/util.js b/lib/util.js index 3d187d3..5720a3f 100644 --- a/lib/util.js +++ b/lib/util.js @@ -34,7 +34,7 @@ const Util = { } else { const tokens = req.headers["authorization"].split(" "); if (tokens[0] === "Basic") { - const splitHash = new Buffer(tokens[1], 'base64').toString('utf8').split(":"); + const splitHash = new Buffer.from(tokens[1], 'base64').toString('utf8').split(":"); const username = splitHash.shift(); const password = splitHash.join(":"); callback(username, password, null); diff --git a/test/fixtures/server/tmp/test.git/hooks/applypatch-msg.sample b/test/fixtures/server/tmp/test.git/hooks/applypatch-msg.sample old mode 100755 new mode 100644 diff --git a/test/fixtures/server/tmp/test.git/hooks/commit-msg.sample b/test/fixtures/server/tmp/test.git/hooks/commit-msg.sample old mode 100755 new mode 100644 diff --git a/test/fixtures/server/tmp/test.git/hooks/post-update.sample b/test/fixtures/server/tmp/test.git/hooks/post-update.sample old mode 100755 new mode 100644 diff --git a/test/fixtures/server/tmp/test.git/hooks/pre-applypatch.sample b/test/fixtures/server/tmp/test.git/hooks/pre-applypatch.sample old mode 100755 new mode 100644 diff --git a/test/fixtures/server/tmp/test.git/hooks/pre-commit.sample b/test/fixtures/server/tmp/test.git/hooks/pre-commit.sample old mode 100755 new mode 100644 diff --git a/test/fixtures/server/tmp/test.git/hooks/pre-push.sample b/test/fixtures/server/tmp/test.git/hooks/pre-push.sample old mode 100755 new mode 100644 diff --git a/test/fixtures/server/tmp/test.git/hooks/pre-rebase.sample b/test/fixtures/server/tmp/test.git/hooks/pre-rebase.sample old mode 100755 new mode 100644 diff --git a/test/fixtures/server/tmp/test.git/hooks/prepare-commit-msg.sample b/test/fixtures/server/tmp/test.git/hooks/prepare-commit-msg.sample old mode 100755 new mode 100644 diff --git a/test/fixtures/server/tmp/test.git/hooks/update.sample b/test/fixtures/server/tmp/test.git/hooks/update.sample old mode 100755 new mode 100644 From 21dc88ee293a3130bd1af2a30da3c1d6e9714d47 Mon Sep 17 00:00:00 2001 From: Gabriel Csapo Date: Wed, 30 May 2018 20:42:04 -0700 Subject: [PATCH 12/26] 0.4.3 - removes deprecated Buffer interface --- CHANGELOG.md | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bf0891..7f786fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 0.4.3 (04/30/2018) + +- removes deprecated `Buffer` interface + # 0.4.2 (12/07/2017) - adds https support diff --git a/package.json b/package.json index 5263a6b..f1fb260 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-git-server", - "version": "0.4.2", + "version": "0.4.3", "description": "🎡 A configurable git server written in Node.js", "author": "Gabriel J. Csapo ", "contributors": [ From f19a1c16dfc1f11f6ca6fca36da923c59dbf3b99 Mon Sep 17 00:00:00 2001 From: Dimitri Date: Tue, 27 Nov 2018 01:50:16 +0100 Subject: [PATCH 13/26] Change to support node-git-server on windows as per (#41) https://github.com/substack/pushover/issues/34 --- lib/service.js | 8 +++++++- lib/util.js | 9 ++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/service.js b/lib/service.js index 4af2f20..2f1cfdd 100644 --- a/lib/service.js +++ b/lib/service.js @@ -96,7 +96,13 @@ class Service extends HttpDuplex { self.once('accept', function onAccept() { process.nextTick(function() { - var cmd = ['git-' + opts.service, '--stateless-rpc', opts.cwd]; + var cmd = null; + var isWin = /^win/.test(process.platform); + if (isWin) { + cmd = ['git', opts.service, '--stateless-rpc', opts.cwd]; + } else { + cmd = ['git-' + opts.service, '--stateless-rpc', opts.cwd]; + } var ps = spawn(cmd[0], cmd.slice(1)); ps.on('error', function(err) { self.emit('error', new Error(`${err.message} running command ${cmd.join(' ')}`)); diff --git a/lib/util.js b/lib/util.js index 5720a3f..04635a4 100644 --- a/lib/util.js +++ b/lib/util.js @@ -84,7 +84,14 @@ const Util = { res.write(pack('# service=git-' + service + '\n')); res.write('0000'); - const cmd = ['git-' + service, '--stateless-rpc', '--advertise-refs', repoLocation]; + var cmd = null; + var isWin = /^win/.test(process.platform); + if (isWin) { + cmd = ['git', service, '--stateless-rpc', '--advertise-refs', repoLocation]; + } else { + cmd = ['git-' + service, '--stateless-rpc', '--advertise-refs', repoLocation]; + } + const ps = spawn(cmd[0], cmd.slice(1)); ps.on('error', (err) => { dup.emit('error', new Error(`${err.message} running command ${cmd.join(' ')}`)); From dd20baf1d6fbe5f81cb5426fdf0bc9973742f16e Mon Sep 17 00:00:00 2001 From: "Gabriel J. Csapo" Date: Tue, 27 Nov 2018 00:41:58 -0500 Subject: [PATCH 14/26] 0.5.0 - adds functionality for event streams and response streams --- CHANGELOG.md | 4 + README.md | 105 +++- example/index.js | 16 +- lib/service.js | 56 +- package.json | 2 +- test/git.js | 1451 ++++++++++++++++++++++++++-------------------- 6 files changed, 989 insertions(+), 645 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f786fb..899d9db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 0.5.0 (11/27/2018) + +- adds `log` functionality for event streams and response streams + # 0.4.3 (04/30/2018) - removes deprecated `Buffer` interface diff --git a/README.md b/README.md index 9cbec6a..c897e9d 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,109 @@ npm install node-git-server # Usage +## Simple + +```javascript +const path = require('path'); +const Server = require('node-git-server'); + +const repos = new Server(path.resolve(__dirname, 'tmp'), { + autoCreate: true +}); +const port = process.env.PORT || 7005; + +repos.on('push', (push) => { + console.log(`push ${push.repo}/${push.commit} (${push.branch})`); + push.accept(); +}); + +repos.on('fetch', (fetch) => { + console.log(`fetch ${fetch.commit}`); + fetch.accept(); +}); + +repos.listen(port, () => { + console.log(`node-git-server running at http://localhost:${port}`) +}); +``` + +then start up the node-git-server server... + +``` +$ node example/index.js +``` + +meanwhile... + +``` +$ git push http://localhost:7005/beep master +Counting objects: 356, done. +Delta compression using up to 2 threads. +Compressing objects: 100% (133/133), done. +Writing objects: 100% (356/356), 46.20 KiB, done. +Total 356 (delta 210), reused 355 (delta 210) +To http://localhost:7005/beep + * [new branch] master -> master +``` + +## Sending logs + +```javascript +const path = require('path'); +const Server = require('node-git-server'); + +const repos = new Server(path.resolve(__dirname, 'tmp'), { + autoCreate: true +}); +const port = process.env.PORT || 7005; + +repos.on('push', (push) => { + console.log(`push ${push.repo}/${push.commit} (${push.branch})`); + + repos.list((err, results) => { + push.log(' '); + push.log('Hey!'); + push.log('Checkout these other repos:'); + for(const repo of results) { + push.log(`- ${repo}`); + } + push.log(' '); + }); + + push.accept(); +}); + +repos.listen(port, () => { + console.log(`node-git-server running at http://localhost:${port}`) +}); +``` + +then start up the node-git-server server... + +``` +$ node example/index.js +``` + +meanwhile... + +``` +$ git push http://localhost:7005/beep master +Counting objects: 356, done. +Delta compression using up to 2 threads. +Compressing objects: 100% (133/133), done. +Writing objects: 100% (356/356), 46.20 KiB, done. +Total 356 (delta 210), reused 355 (delta 210) +remote: +remote: Hey! +remote: Checkout these other repos: +remote: - test.git +remote: +To http://localhost:7005/test + 77bb26e..22918d5 master -> master +``` + +### Authentication + ```javascript const path = require('path'); const Server = require('node-git-server'); @@ -73,7 +176,7 @@ To http://localhost:7005/beep * [new branch] master -> master ``` -## Example +# Example Running the following command will start up a simple http server: diff --git a/example/index.js b/example/index.js index 340f3f1..1e856d8 100644 --- a/example/index.js +++ b/example/index.js @@ -39,9 +39,17 @@ const repos = new Server(path.normalize(path.resolve(__dirname, 'tmp')), { repos.on('push', (push) => { console.log(`push ${push.repo} / ${push.commit} ( ${push.branch} )`); // eslint-disable-line - repos.list((err, result) => { - console.log(result); // eslint-disable-line + + repos.list((err, results) => { + push.log(' '); + push.log('Hey!'); + push.log('Checkout these other repos:'); + for(const repo of results) { + push.log(`- ${repo}`); + } + push.log(' '); }); + push.accept(); }); @@ -53,8 +61,8 @@ repos.on('fetch', (fetch) => { repos.listen(port, { type, - key: fs.readFileSync('./privatekey.pem'), - cert: fs.readFileSync('./certificate.pem') + key: fs.readFileSync(path.resolve(__dirname, 'privatekey.pem')), + cert: fs.readFileSync(path.resolve(__dirname, 'certificate.pem')) }, (error) => { if(error) return console.error(`failed to start git-server because of error ${error}`); // eslint-disable-line console.log(`node-git-server running at ${type}://localhost:${port}`); // eslint-disable-line diff --git a/lib/service.js b/lib/service.js index 4af2f20..2ccd254 100644 --- a/lib/service.js +++ b/lib/service.js @@ -1,14 +1,20 @@ const through = require('through'); -const HttpDuplex = require('./http-duplex'); const zlib = require('zlib'); - +const util = require('util'); const { spawn } = require('child_process'); +const HttpDuplex = require('./http-duplex'); + const headerRE = { 'receive-pack': '([0-9a-fA-F]+) ([0-9a-fA-F]+) refs\/(heads|tags)\/(.*?)( |00|\u0000)|^(0000)$', // eslint-disable-line 'upload-pack': '^\\S+ ([0-9a-fA-F]+)' }; +const packSideband = s => { + const n = (4 + s.length).toString(16); + return Array(4 - n.length + 1).join('0') + n + s; +}; + class Service extends HttpDuplex { /** * Handles invoking the git-*-pack binaries @@ -28,6 +34,7 @@ class Service extends HttpDuplex { this.repo = opts.repo; this.service = opts.service; this.cwd = opts.cwd; + this.logs = []; var buffered = through().pause(); @@ -96,35 +103,59 @@ class Service extends HttpDuplex { self.once('accept', function onAccept() { process.nextTick(function() { - var cmd = ['git-' + opts.service, '--stateless-rpc', opts.cwd]; - var ps = spawn(cmd[0], cmd.slice(1)); + const cmd = ['git-' + opts.service, '--stateless-rpc', opts.cwd]; + const ps = spawn(cmd[0], cmd.slice(1)); + ps.on('error', function(err) { self.emit('error', new Error(`${err.message} running command ${cmd.join(' ')}`)); }); self.emit('service', ps); - var respStream = through(function(c) { - if (self.listeners('response').length === 0) return this.queue(c); + var respStream = through(function write(c) { + if (self.listeners('response').length === 0) { + if(self.logs.length > 0) { + while(self.logs.length > 0) { + this.queue(self.logs.pop()); + } + } + + return this.queue(c); + } // prevent git from sending the close signal if (c.length === 4 && c.toString() === '0000') return; this.queue(c); - }, function() { + }, function end() { if (self.listeners('response').length > 0) return; + this.queue(null); }); + respStream.log = function() { + self.log(...arguments); + }; self.emit('response', respStream, function endResponse() { res.queue(new Buffer.from('0000')); res.queue(null); }); + ps.stdout.pipe(respStream).pipe(res); buffered.pipe(ps.stdin); buffered.resume(); - ps.on('exit', self.emit.bind(self, 'exit')); + ps.on('exit', () => { + if(self.logs.length > 0) { + while(self.logs.length > 0) { + respStream.queue(self.logs.pop()); + } + respStream.queue(Buffer.from('0000')); + respStream.queue(null); + } + + self.emit.bind(self, 'exit'); + }); }); }); @@ -133,6 +164,15 @@ class Service extends HttpDuplex { res.end(msg); }); } + + log() { + const _log = util.format(...arguments); + const SIDEBAND = String.fromCharCode(2); // PROGRESS + const message = `${SIDEBAND}${_log}\n`; + const formattedMessage = Buffer.from(packSideband(message)); + + this.logs.unshift(formattedMessage); + } /** * reject request in flight * @method reject diff --git a/package.json b/package.json index f1fb260..339323e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-git-server", - "version": "0.4.3", + "version": "0.5.0", "description": "🎡 A configurable git server written in Node.js", "author": "Gabriel J. Csapo ", "contributors": [ diff --git a/test/git.js b/test/git.js index 7294834..db300c2 100644 --- a/test/git.js +++ b/test/git.js @@ -10,284 +10,288 @@ const async = require('async'); const GitServer = require('../'); test('git', (t) => { - t.plan(6); + t.plan(8); t.test('create, push to, and clone a repo', (t) => { - var lastCommit; - - const repoDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; - const srcDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; - const dstDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; - - fs.mkdirSync(repoDir, 0700); - fs.mkdirSync(srcDir, 0700); - fs.mkdirSync(dstDir, 0700); - - const repos = new GitServer(repoDir, { autoCreate : true }); - const port = Math.floor(Math.random() * ((1<<16) - 1e4)) + 1e4; - const server = http.createServer((req, res) => { - repos.handle(req, res); - }).listen(port); - - process.chdir(srcDir); - - async.waterfall([ - (callback) => { - repos.mkdir('xyz', () => { - callback(); - }); - }, - (callback) => { - repos.create('xyz/doom', () => { - callback(); - }); - }, - (callback) => { - spawn('git', [ 'init' ]) - .on('exit', (code) => { - t.equal(code, 0); - callback(); - }); - }, - (callback) => { - fs.writeFile(srcDir + '/a.txt', 'abcd', () => { - callback(); - }); - }, - (callback) => { - spawn('git', [ 'add', 'a.txt' ]) - .on('exit', (code) => { - t.equal(code, 0); - callback(); - }); - }, - (callback) => { - spawn('git', [ 'commit', '-am', 'a!!' ]) - .on('exit', () => { - exec('git log | head -n1', (err, stdout) => { - lastCommit = stdout.split(/\s+/)[1]; - callback(); - }); - }); - }, - (callback) => { - spawn('git', [ - 'push', 'http://localhost:' + port + '/xyz/doom', 'master' - ]) - .on('exit', (code) => { - t.equal(code, 0); - callback(); - }); - }, - (callback) => { - process.chdir(dstDir); - spawn('git', [ 'clone', 'http://localhost:' + port + '/xyz/doom' ]) - .on('exit', (code) => { - t.equal(code, 0); - callback(); - }); - }, - (callback) => { - fs.exists(dstDir + '/doom/a.txt', (ex) => { - t.ok(ex, 'a.txt exists'); - callback(); - }); - } - ], (err) => { - t.ok(!err, 'no errors'); - server.close(); - t.end(); - }); + var lastCommit; - repos.on('push', (push) => { - t.equal(push.repo, 'xyz/doom', 'repo name'); - t.equal(push.commit, lastCommit, 'commit ok'); - t.equal(push.branch, 'master', 'master branch'); + const repoDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; + const srcDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; + const dstDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; - t.equal(push.headers.host, 'localhost:' + port, 'http host'); - t.equal(push.method, 'POST', 'is a post'); - t.equal(push.url, '/xyz/doom/git-receive-pack', 'receive pack'); + fs.mkdirSync(repoDir, 0700); + fs.mkdirSync(srcDir, 0700); + fs.mkdirSync(dstDir, 0700); - push.accept(); - }); + const repos = new GitServer(repoDir, { + autoCreate: true + }); + const port = Math.floor(Math.random() * ((1 << 16) - 1e4)) + 1e4; + const server = http.createServer((req, res) => { + repos.handle(req, res); + }).listen(port); + + process.chdir(srcDir); + + async.waterfall([ + (callback) => { + repos.mkdir('xyz', () => { + callback(); + }); + }, + (callback) => { + repos.create('xyz/doom', () => { + callback(); + }); + }, + (callback) => { + spawn('git', ['init']) + .on('exit', (code) => { + t.equal(code, 0); + callback(); + }); + }, + (callback) => { + fs.writeFile(srcDir + '/a.txt', 'abcd', () => { + callback(); + }); + }, + (callback) => { + spawn('git', ['add', 'a.txt']) + .on('exit', (code) => { + t.equal(code, 0); + callback(); + }); + }, + (callback) => { + spawn('git', ['commit', '-am', 'a!!']) + .on('exit', () => { + exec('git log | head -n1', (err, stdout) => { + lastCommit = stdout.split(/\s+/)[1]; + callback(); + }); + }); + }, + (callback) => { + spawn('git', [ + 'push', 'http://localhost:' + port + '/xyz/doom', 'master' + ]) + .on('exit', (code) => { + t.equal(code, 0); + callback(); + }); + }, + (callback) => { + process.chdir(dstDir); + spawn('git', ['clone', 'http://localhost:' + port + '/xyz/doom']) + .on('exit', (code) => { + t.equal(code, 0); + callback(); + }); + }, + (callback) => { + fs.exists(dstDir + '/doom/a.txt', (ex) => { + t.ok(ex, 'a.txt exists'); + callback(); + }); + } + ], (err) => { + t.ok(!err, 'no errors'); + server.close(); + t.end(); + }); + + repos.on('push', (push) => { + t.equal(push.repo, 'xyz/doom', 'repo name'); + t.equal(push.commit, lastCommit, 'commit ok'); + t.equal(push.branch, 'master', 'master branch'); + + t.equal(push.headers.host, 'localhost:' + port, 'http host'); + t.equal(push.method, 'POST', 'is a post'); + t.equal(push.url, '/xyz/doom/git-receive-pack', 'receive pack'); + + push.accept(); + }); }); + + t.test('create, push to, and clone a repo successful', (t) => { - t.plan(9); + t.plan(9); - const repoDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; - const srcDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; - const dstDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; + const repoDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; + const srcDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; + const dstDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; - fs.mkdirSync(repoDir, 0700); - fs.mkdirSync(srcDir, 0700); - fs.mkdirSync(dstDir, 0700); + fs.mkdirSync(repoDir, 0700); + fs.mkdirSync(srcDir, 0700); + fs.mkdirSync(dstDir, 0700); - const repos = new GitServer(repoDir); - const port = Math.floor(Math.random() * ((1 << 16) - 1e4)) + 1e4; - const server = http.createServer((req, res) => { - repos.handle(req, res); - }); - server.listen(port); - - process.chdir(srcDir); - async.waterfall([ - (callback) => { - spawn('git', ['init']) - .on('exit', (code) => { - t.equal(code, 0); - callback(); - }); - }, - (callback) => { - fs.writeFile(srcDir + '/a.txt', 'abcd', (err) => { - t.ok(!err, 'no error on write'); - callback(); - }); - }, - (callback) => { - spawn('git', ['add', 'a.txt']) - .on('exit', (code) => { - t.equal(code, 0); - callback(); - }); - }, - (callback) => { - spawn('git', ['commit', '-am', 'a!!']) - .on('exit', (code) => { - t.equal(code, 0); - callback(); - }); - }, - (callback) => { - spawn('git', [ - 'push', 'http://localhost:' + port + '/doom', 'master' - ]) - .on('exit', (code) => { - t.equal(code, 0); - callback(); - }); - }, - (callback) => { - process.chdir(dstDir); - spawn('git', ['clone', 'http://localhost:' + port + '/doom']) - .on('exit', (code) => { - t.equal(code, 0); - callback(); - }); - }, - (callback) => { - fs.stat(dstDir + '/doom/a.txt', (ex) => { - t.ok(!ex, 'a.txt exists'); - callback(); - }); - } - ], (err) => { - t.ok(!err, 'no errors'); - server.close(); - t.end(); - }); + const repos = new GitServer(repoDir); + const port = Math.floor(Math.random() * ((1 << 16) - 1e4)) + 1e4; + const server = http.createServer((req, res) => { + repos.handle(req, res); + }); + server.listen(port); - repos.on('push', (push) => { - t.equal(push.repo, 'doom'); - push.accept(); - }); + process.chdir(srcDir); + async.waterfall([ + (callback) => { + spawn('git', ['init']) + .on('exit', (code) => { + t.equal(code, 0); + callback(); + }); + }, + (callback) => { + fs.writeFile(srcDir + '/a.txt', 'abcd', (err) => { + t.ok(!err, 'no error on write'); + callback(); + }); + }, + (callback) => { + spawn('git', ['add', 'a.txt']) + .on('exit', (code) => { + t.equal(code, 0); + callback(); + }); + }, + (callback) => { + spawn('git', ['commit', '-am', 'a!!']) + .on('exit', (code) => { + t.equal(code, 0); + callback(); + }); + }, + (callback) => { + spawn('git', [ + 'push', 'http://localhost:' + port + '/doom', 'master' + ]) + .on('exit', (code) => { + t.equal(code, 0); + callback(); + }); + }, + (callback) => { + process.chdir(dstDir); + spawn('git', ['clone', 'http://localhost:' + port + '/doom']) + .on('exit', (code) => { + t.equal(code, 0); + callback(); + }); + }, + (callback) => { + fs.stat(dstDir + '/doom/a.txt', (ex) => { + t.ok(!ex, 'a.txt exists'); + callback(); + }); + } + ], (err) => { + t.ok(!err, 'no errors'); + server.close(); + t.end(); + }); + + repos.on('push', (push) => { + t.equal(push.repo, 'doom'); + push.accept(); + }); }); test('clone into programatic directories', (t) => { - t.plan(21); - - const port = Math.floor(Math.random() * ((1 << 16) - 1e4)) + 1e4; - const repoDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; - const srcDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; - const dstDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; - const targetDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; - - fs.mkdirSync(repoDir, 0700); - fs.mkdirSync(srcDir, 0700); - fs.mkdirSync(dstDir, 0700); - fs.mkdirSync(targetDir, 0700); - - const server = new GitServer((dir) => { - t.equal(dir, 'doom.git'); - return path.join(targetDir, dir); - }); - server.listen(port); - - process.chdir(srcDir); - async.waterfall([ - (callback) => { - spawn('git', [ 'init' ]) - .on('exit', (code) => { - t.equal(code, 0); - callback(); - }); - }, - (callback) => { - fs.writeFile(srcDir + '/a.txt', 'abcd', (err) => { - t.ok(!err, 'no error on write'); - callback(); - }); - }, - (callback) => { - spawn('git', [ 'add', 'a.txt' ], { - cwd: srcDir - }) - .on('exit', (code) => { - t.equal(code, 0); - callback(); - }); - }, - (callback) => { - spawn('git', [ 'commit', '-am', 'a!!' ], { - cwd: srcDir - }) - .on('exit', (code) => { - t.equal(code, 0); - callback(); - }); - }, - (callback) => { - spawn('git', [ - 'push', 'http://localhost:' + port + '/doom.git', 'master' - ], { - cwd: srcDir - }) - .on('exit', (code) => { - t.equal(code, 0); - callback(); - }); - }, - (callback) => { - process.chdir(dstDir); - spawn('git', [ 'clone', 'http://localhost:' + port + '/doom.git' ]) - .on('exit', (code) => { - t.equal(code, 0); - callback(); - }); - }, - (callback) => { - fs.stat(dstDir + '/doom/a.txt', (ex) => { - t.ok(!ex, 'a.txt exists'); - callback(); - }); - }, - (callback) => { - fs.stat(targetDir + '/doom.git/HEAD', (ex) => { - t.ok(!ex, 'INFO exists'); - callback(); - }); - } - ], (err) => { - t.ok(!err, 'no errors'); - server.close(); - t.end(); - }); + t.plan(21); - server.on('push', (push) => { - t.equal(push.repo, 'doom.git'); - push.accept(); - }); + const port = Math.floor(Math.random() * ((1 << 16) - 1e4)) + 1e4; + const repoDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; + const srcDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; + const dstDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; + const targetDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; + + fs.mkdirSync(repoDir, 0700); + fs.mkdirSync(srcDir, 0700); + fs.mkdirSync(dstDir, 0700); + fs.mkdirSync(targetDir, 0700); + + const server = new GitServer((dir) => { + t.equal(dir, 'doom.git'); + return path.join(targetDir, dir); + }); + server.listen(port); + + process.chdir(srcDir); + async.waterfall([ + (callback) => { + spawn('git', ['init']) + .on('exit', (code) => { + t.equal(code, 0); + callback(); + }); + }, + (callback) => { + fs.writeFile(srcDir + '/a.txt', 'abcd', (err) => { + t.ok(!err, 'no error on write'); + callback(); + }); + }, + (callback) => { + spawn('git', ['add', 'a.txt'], { + cwd: srcDir + }) + .on('exit', (code) => { + t.equal(code, 0); + callback(); + }); + }, + (callback) => { + spawn('git', ['commit', '-am', 'a!!'], { + cwd: srcDir + }) + .on('exit', (code) => { + t.equal(code, 0); + callback(); + }); + }, + (callback) => { + spawn('git', [ + 'push', 'http://localhost:' + port + '/doom.git', 'master' + ], { + cwd: srcDir + }) + .on('exit', (code) => { + t.equal(code, 0); + callback(); + }); + }, + (callback) => { + process.chdir(dstDir); + spawn('git', ['clone', 'http://localhost:' + port + '/doom.git']) + .on('exit', (code) => { + t.equal(code, 0); + callback(); + }); + }, + (callback) => { + fs.stat(dstDir + '/doom/a.txt', (ex) => { + t.ok(!ex, 'a.txt exists'); + callback(); + }); + }, + (callback) => { + fs.stat(targetDir + '/doom.git/HEAD', (ex) => { + t.ok(!ex, 'INFO exists'); + callback(); + }); + } + ], (err) => { + t.ok(!err, 'no errors'); + server.close(); + t.end(); + }); + + server.on('push', (push) => { + t.equal(push.repo, 'doom.git'); + push.accept(); + }); }); test('test tagging', (t) => { @@ -303,439 +307,624 @@ test('git', (t) => { fs.mkdirSync(dstDir, 0700); const repos = new GitServer(repoDir, { - autoCreate: true + autoCreate: true }); const port = Math.floor(Math.random() * ((1 << 16) - 1e4)) + 1e4; const server = http.createServer((req, res) => { - repos.handle(req, res); + repos.handle(req, res); }); server.listen(port); process.chdir(srcDir); async.waterfall([ - (callback) => { - repos.create('doom', () => { - callback(); - }); - }, - (callback) => { - spawn('git', ['init']) - .on('exit', (code) => { - t.equal(code, 0); - callback(); - }); - }, - (callback) => { - fs.writeFile(srcDir + '/a.txt', 'abcd', (err) => { - t.ok(!err, 'no error on write'); - callback(); - }); - }, - (callback) => { - spawn('git', ['add', 'a.txt']) - .on('exit', (code) => { - t.equal(code, 0); - callback(); - }); - }, - (callback) => { - spawn('git', ['commit', '-am', 'a!!']) - .on('exit', (code) => { - t.equal(code, 0); - callback(); - }); - }, - (callback) => { - spawn('git', ['tag', '0.0.1']) - .on('exit', (code) => { - t.equal(code, 0); - callback(); - }); - }, - (callback) => { - fs.writeFile(srcDir + '/a.txt', 'efgh', (err) => { - t.ok(!err, 'no error on write'); - callback(); - }); - }, - (callback) => { - spawn('git', ['add', 'a.txt']) - .on('exit', (code) => { - t.equal(code, 0); - callback(); - }); - }, - (callback) => { - spawn('git', ['commit', '-am', 'a!!']) - .on('exit', () => { - exec('git log | head -n1', (err, stdout) => { - lastCommit = stdout.split(/\s+/)[1]; - callback(); - }); - }); - }, - (callback) => { - spawn('git', ['tag', '0.0.2']) - .on('exit', (code) => { - t.equal(code, 0); - callback(); - }); - }, - (callback) => { - spawn('git', [ - 'push', '--tags', 'http://localhost:' + port + '/doom', 'master' - ]) - .on('exit', (code) => { - t.equal(code, 0); - callback(); - }); - }, - (callback) => { - process.chdir(dstDir); - spawn('git', ['clone', 'http://localhost:' + port + '/doom']) - .on('exit', (code) => { - t.equal(code, 0); - callback(); - }); - }, - (callback) => { - fs.exists(dstDir + '/doom/a.txt', (ex) => { - t.ok(ex, 'a.txt exists'); - callback(); + (callback) => { + repos.create('doom', () => { + callback(); + }); + }, + (callback) => { + spawn('git', ['init']) + .on('exit', (code) => { + t.equal(code, 0); + callback(); + }); + }, + (callback) => { + fs.writeFile(srcDir + '/a.txt', 'abcd', (err) => { + t.ok(!err, 'no error on write'); + callback(); + }); + }, + (callback) => { + spawn('git', ['add', 'a.txt']) + .on('exit', (code) => { + t.equal(code, 0); + callback(); + }); + }, + (callback) => { + spawn('git', ['commit', '-am', 'a!!']) + .on('exit', (code) => { + t.equal(code, 0); + callback(); + }); + }, + (callback) => { + spawn('git', ['tag', '0.0.1']) + .on('exit', (code) => { + t.equal(code, 0); + callback(); + }); + }, + (callback) => { + fs.writeFile(srcDir + '/a.txt', 'efgh', (err) => { + t.ok(!err, 'no error on write'); + callback(); + }); + }, + (callback) => { + spawn('git', ['add', 'a.txt']) + .on('exit', (code) => { + t.equal(code, 0); + callback(); + }); + }, + (callback) => { + spawn('git', ['commit', '-am', 'a!!']) + .on('exit', () => { + exec('git log | head -n1', (err, stdout) => { + lastCommit = stdout.split(/\s+/)[1]; + callback(); }); - } + }); + }, + (callback) => { + spawn('git', ['tag', '0.0.2']) + .on('exit', (code) => { + t.equal(code, 0); + callback(); + }); + }, + (callback) => { + spawn('git', [ + 'push', '--tags', 'http://localhost:' + port + '/doom', 'master' + ]) + .on('exit', (code) => { + t.equal(code, 0); + callback(); + }); + }, + (callback) => { + process.chdir(dstDir); + spawn('git', ['clone', 'http://localhost:' + port + '/doom']) + .on('exit', (code) => { + t.equal(code, 0); + callback(); + }); + }, + (callback) => { + fs.exists(dstDir + '/doom/a.txt', (ex) => { + t.ok(ex, 'a.txt exists'); + callback(); + }); + } ], (err) => { - t.ok(!err, 'no errors'); - server.close(); - t.end(); + t.ok(!err, 'no errors'); + server.close(); + t.end(); }); repos.on('push', (push) => { - t.equal(push.repo, 'doom', 'repo name'); - t.equal(push.commit, lastCommit, 'commit ok'); - t.equal(push.branch, 'master', 'master branch'); + t.equal(push.repo, 'doom', 'repo name'); + t.equal(push.commit, lastCommit, 'commit ok'); + t.equal(push.branch, 'master', 'master branch'); - t.equal(push.headers.host, 'localhost:' + port, 'http host'); - t.equal(push.method, 'POST', 'is a post'); - t.equal(push.url, '/doom/git-receive-pack', 'receive pack'); + t.equal(push.headers.host, 'localhost:' + port, 'http host'); + t.equal(push.method, 'POST', 'is a post'); + t.equal(push.url, '/doom/git-receive-pack', 'receive pack'); - push.accept(); + push.accept(); }); var firstTag = true; repos.on('tag', (tag) => { - t.equal(tag.repo, 'doom', 'repo name'); - t.equal(tag.version, '0.0.' + (firstTag ? 1 : 2), 'tag received'); + t.equal(tag.repo, 'doom', 'repo name'); + t.equal(tag.version, '0.0.' + (firstTag ? 1 : 2), 'tag received'); - t.equal(tag.headers.host, 'localhost:' + port, 'http host'); - t.equal(tag.method, 'POST', 'is a post'); - t.equal(tag.url, '/doom/git-receive-pack', 'receive pack'); + t.equal(tag.headers.host, 'localhost:' + port, 'http host'); + t.equal(tag.method, 'POST', 'is a post'); + t.equal(tag.url, '/doom/git-receive-pack', 'receive pack'); - tag.accept(); - firstTag = false; + tag.accept(); + firstTag = false; }); -}); + }); t.test('repos list', (t) => { + t.plan(2); + + const workingRepoDir = path.resolve(__dirname, 'fixtures', 'server', 'tmp'); + const notWorkingRepoDir = path.resolve(__dirname, 'fixtures', 'server', 'temp'); + + t.test('should return back with one directory in server', (t) => { t.plan(2); - const workingRepoDir = path.resolve(__dirname, 'fixtures', 'server', 'tmp'); - const notWorkingRepoDir = path.resolve(__dirname, 'fixtures', 'server', 'temp'); + const repos = new GitServer(workingRepoDir, { + autoCreate: true + }); - t.test('should return back with one directory in server', (t) => { - const repos = new GitServer(workingRepoDir, { - autoCreate: true - }); - repos.list((err, results) => { - t.ok(err === null, 'there is no error'); - t.deepEqual(['test.git'], results); - t.end(); - }); + repos.list((err, results) => { + t.ok(err === null, 'there is no error'); + t.deepEqual(['test.git'], results); + t.end(); }); + }); - t.test('should return back error directory does not exist', (t) => { - const repos = new GitServer(notWorkingRepoDir, { - autoCreate: true - }); - repos.list((err, results) => { - t.ok(err !== null, 'there is an error'); - t.ok(results === undefined); - t.end(); - }); + t.test('should return back error directory does not exist', (t) => { + t.plan(2); + + const repos = new GitServer(notWorkingRepoDir, { + autoCreate: true }); - t.end(); + repos.list((err, results) => { + t.ok(err !== null, 'there is an error'); + t.ok(results === undefined); + t.end(); + }); + }); + + t.end(); }); test('create, push to, and clone a repo reject', (t) => { - t.plan(13); - - function _spawn(cmd, args, opts) { - var ps = spawn(cmd, args, opts); - ps.on('error', (err) => { - console.error( // eslint-disable-line - err.message + ' while executing: ' - + cmd + ' ' + args.join(' ') - ); - }); - return ps; - } + t.plan(13); + + function _spawn(cmd, args, opts) { + var ps = spawn(cmd, args, opts); + ps.on('error', (err) => { + console.error( // eslint-disable-line + err.message + ' while executing: ' + + cmd + ' ' + args.join(' ') + ); + }); + return ps; + } - var lastCommit; + var lastCommit; - const repoDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; - const srcDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; - const dstDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; + const repoDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; + const srcDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; + const dstDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; - fs.mkdirSync(repoDir, 0700); - fs.mkdirSync(srcDir, 0700); - fs.mkdirSync(dstDir, 0700); + fs.mkdirSync(repoDir, 0700); + fs.mkdirSync(srcDir, 0700); + fs.mkdirSync(dstDir, 0700); - const repos = new GitServer(repoDir, { autoCreate : true }); - const port = Math.floor(Math.random() * ((1<<16) - 1e4)) + 1e4; - const server = http.createServer((req, res) => { - repos.handle(req, res); - }); - server.listen(port); + const repos = new GitServer(repoDir, { + autoCreate: true + }); + const port = Math.floor(Math.random() * ((1 << 16) - 1e4)) + 1e4; + const server = http.createServer((req, res) => { + repos.handle(req, res); + }); + server.listen(port); - t.on('end', () => { - server.close(); - }); + t.on('end', () => { + server.close(); + }); - process.chdir(srcDir); - async.waterfall([ - (callback) => { - repos.create('doom', () => { - callback(); - }); - }, - (callback) => { - _spawn('git', [ 'init' ]) - .on('exit', (code) => { - t.equal(code, 0); - callback(); - }); - }, - (callback) => { - fs.writeFile(srcDir + '/a.txt', 'abcd', (err) => { - t.ok(!err, 'no error on write'); - callback(); - }); - }, - (callback) => { - _spawn('git', [ 'add', 'a.txt' ]) - .on('exit', (code) => { - t.equal(code, 0); - callback(); - }); - }, - (callback) => { - _spawn('git', [ 'commit', '-am', 'a!!' ]) - .on('exit', () => { - exec('git log | head -n1', (err, stdout) => { - lastCommit = stdout.split(/\s+/)[1]; - callback(); - }); - }); - }, - (callback) => { - _spawn('git', [ - 'push', 'http://localhost:' + port + '/doom', 'master' - ]) - .on('exit', (code) => { - t.notEqual(code, 0); - callback(); - }); - }, - (callback) => { - const glog = _spawn('git', [ 'log'], { cwd : repoDir + '/doom.git' }); - glog.on('exit', (code) => { - t.equal(code, 128); - callback(); - }); - var data = ''; - glog.stderr.on('data', (buf) => data += buf); - glog.stderr.on('end', () => { - const res = /fatal: bad default revision 'HEAD'/.test(data) || /fatal: your current branch 'master' does not have any commits yet/.test(data); - t.ok(res); - }); - } - ], (err) => { - t.ok(!err, 'no errors'); - server.close(); - t.end(); - }); + process.chdir(srcDir); + async.waterfall([ + (callback) => { + repos.create('doom', () => { + callback(); + }); + }, + (callback) => { + _spawn('git', ['init']) + .on('exit', (code) => { + t.equal(code, 0); + callback(); + }); + }, + (callback) => { + fs.writeFile(srcDir + '/a.txt', 'abcd', (err) => { + t.ok(!err, 'no error on write'); + callback(); + }); + }, + (callback) => { + _spawn('git', ['add', 'a.txt']) + .on('exit', (code) => { + t.equal(code, 0); + callback(); + }); + }, + (callback) => { + _spawn('git', ['commit', '-am', 'a!!']) + .on('exit', () => { + exec('git log | head -n1', (err, stdout) => { + lastCommit = stdout.split(/\s+/)[1]; + callback(); + }); + }); + }, + (callback) => { + _spawn('git', [ + 'push', 'http://localhost:' + port + '/doom', 'master' + ]) + .on('exit', (code) => { + t.notEqual(code, 0); + callback(); + }); + }, + (callback) => { + const glog = _spawn('git', ['log'], { + cwd: repoDir + '/doom.git' + }); + glog.on('exit', (code) => { + t.equal(code, 128); + callback(); + }); + var data = ''; + glog.stderr.on('data', (buf) => data += buf); + glog.stderr.on('end', () => { + const res = /fatal: bad default revision 'HEAD'/.test(data) || /fatal: your current branch 'master' does not have any commits yet/.test(data); + t.ok(res); + }); + } + ], (err) => { + t.ok(!err, 'no errors'); + server.close(); + t.end(); + }); - repos.on('push', (push) => { - t.equal(push.repo, 'doom', 'repo name'); - t.equal(push.commit, lastCommit, 'commit ok'); - t.equal(push.branch, 'master', 'master branch'); + repos.on('push', (push) => { + t.equal(push.repo, 'doom', 'repo name'); + t.equal(push.commit, lastCommit, 'commit ok'); + t.equal(push.branch, 'master', 'master branch'); - t.equal(push.headers.host, 'localhost:' + port, 'http host'); - t.equal(push.method, 'POST', 'is a post'); - t.equal(push.url, '/doom/git-receive-pack', 'receive pack'); + t.equal(push.headers.host, 'localhost:' + port, 'http host'); + t.equal(push.method, 'POST', 'is a post'); + t.equal(push.url, '/doom/git-receive-pack', 'receive pack'); - push.reject(500, 'ACCESS DENIED'); - }); + push.reject(500, 'ACCESS DENIED'); + }); }); t.test('create git server via listen() command', (t) => { + const repoDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; + const srcDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; + const dstDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; - const repoDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; - const srcDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; - const dstDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; - - fs.mkdirSync(repoDir, 0700); - fs.mkdirSync(srcDir, 0700); - fs.mkdirSync(dstDir, 0700); - - const repos = new GitServer(repoDir); - const port = Math.floor(Math.random() * ((1<<16) - 1e4)) + 1e4; - repos.listen(port); - - process.chdir(srcDir); - async.waterfall([ - (callback) => { - process.chdir(dstDir); - spawn('git', [ 'clone', 'http://localhost:' + port + '/doom' ]) - .on('exit', (code) => { - t.equal(code, 0); - callback(); - }); - }, - ], (err) => { - t.ok(!err, 'no errors'); - repos.close(); - t.end(); - }); + fs.mkdirSync(repoDir, 0700); + fs.mkdirSync(srcDir, 0700); + fs.mkdirSync(dstDir, 0700); + + const repos = new GitServer(repoDir); + const port = Math.floor(Math.random() * ((1 << 16) - 1e4)) + 1e4; + repos.listen(port); + + process.chdir(srcDir); + async.waterfall([ + (callback) => { + process.chdir(dstDir); + spawn('git', ['clone', 'http://localhost:' + port + '/doom']) + .on('exit', (code) => { + t.equal(code, 0); + callback(); + }); + }, + ], (err) => { + t.ok(!err, 'no errors'); + repos.close(); + t.end(); + }); }); t.test('should be able to protect certain routes', (t) => { - const repoDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; - const srcDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; - const dstDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; - - fs.mkdirSync(repoDir, 0700); - fs.mkdirSync(srcDir, 0700); - fs.mkdirSync(dstDir, 0700); - - const repos = new GitServer(repoDir, { - autoCreate: true, - authenticate: (type, repo, user, next) => { - - if(type == 'download', repo == 'doom') { - user((username, password) => { - if(username == 'root' && password == 'root') { - next(); - } else { - next('that is not the correct password'); - } - }); + const repoDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; + const srcDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; + const dstDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; + + fs.mkdirSync(repoDir, 0700); + fs.mkdirSync(srcDir, 0700); + fs.mkdirSync(dstDir, 0700); + + const repos = new GitServer(repoDir, { + autoCreate: true, + authenticate: (type, repo, user, next) => { + + if (type == 'download', repo == 'doom') { + user((username, password) => { + if (username == 'root' && password == 'root') { + next(); } else { next('that is not the correct password'); } - } - }); - const port = Math.floor(Math.random() * ((1<<16) - 1e4)) + 1e4; - repos.listen(port); - - process.chdir(srcDir); - async.waterfall([ - (callback) => { - process.chdir(dstDir); - const clone = spawn('git', [ 'clone', `http://root:root@localhost:${port}/doom.git` ]); - - clone.on('close', function(code) { - t.equal(code, 0); - callback(); - }); - }, - (callback) => { - process.chdir(dstDir); - const clone = spawn('git', [ 'clone', `http://root:world@localhost:${port}/doom.git doom1` ]); - let error = ''; - - clone.stderr.on('data', (d) => { - error += d.toString('utf8'); - }); - - clone.on('close', function(code) { - t.equal(error, `Cloning into 'doom.git doom1'...\nfatal: unable to access 'http://root:world@localhost:${port}/doom.git doom1/': Empty reply from server\n`); - t.equal(code, 128); - callback(); - }); - } - ], (err) => { - t.ok(!err, 'no errors'); - repos.close(); - t.end(); - }); + }); + } else { + next('that is not the correct password'); + } + } + }); + const port = Math.floor(Math.random() * ((1 << 16) - 1e4)) + 1e4; + repos.listen(port); + + process.chdir(srcDir); + async.waterfall([ + (callback) => { + process.chdir(dstDir); + const clone = spawn('git', ['clone', `http://root:root@localhost:${port}/doom.git`]); + + clone.on('close', function(code) { + t.equal(code, 0); + callback(); + }); + }, + (callback) => { + process.chdir(dstDir); + const clone = spawn('git', ['clone', `http://root:world@localhost:${port}/doom.git doom1`]); + let error = ''; + + clone.stderr.on('data', (d) => { + error += d.toString('utf8'); + }); + + clone.on('close', function(code) { + t.equal(error, `Cloning into 'doom.git doom1'...\nfatal: unable to access 'http://root:world@localhost:${port}/doom.git doom1/': Empty reply from server\n`); + t.equal(code, 128); + callback(); + }); + } + ], (err) => { + t.ok(!err, 'no errors'); + repos.close(); + t.end(); + }); }); t.test('should be able to protect certain routes with a promised authenticate', (t) => { - const repoDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; - const srcDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; - const dstDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; - - fs.mkdirSync(repoDir, 0700); - fs.mkdirSync(srcDir, 0700); - fs.mkdirSync(dstDir, 0700); - - const repos = new GitServer(repoDir, { - autoCreate: true, - authenticate: (type, repo, user) => { - return new Promise(function(resolve, reject) { - if(type == 'download', repo == 'doom') { - user((username, password) => { - if(username == 'root' && password == 'root') { - return resolve(); - } else { - return reject('that is not the correct password'); - } - }); + const repoDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; + const srcDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; + const dstDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; + + fs.mkdirSync(repoDir, 0700); + fs.mkdirSync(srcDir, 0700); + fs.mkdirSync(dstDir, 0700); + + const repos = new GitServer(repoDir, { + autoCreate: true, + authenticate: (type, repo, user) => { + return new Promise(function(resolve, reject) { + if (type == 'download', repo == 'doom') { + user((username, password) => { + if (username == 'root' && password == 'root') { + return resolve(); } else { return reject('that is not the correct password'); } }); + } else { + return reject('that is not the correct password'); } - }); - const port = Math.floor(Math.random() * ((1<<16) - 1e4)) + 1e4; - repos.listen(port); - - process.chdir(srcDir); - async.waterfall([ - (callback) => { - process.chdir(dstDir); - const clone = spawn('git', [ 'clone', `http://root:root@localhost:${port}/doom.git` ]); - - clone.on('close', function(code) { - t.equal(code, 0); - callback(); - }); - }, - (callback) => { - process.chdir(dstDir); - const clone = spawn('git', [ 'clone', `http://root:world@localhost:${port}/doom.git doom1` ]); - let error = ''; - - clone.stderr.on('data', (d) => { - error += d.toString('utf8'); - }); - - clone.on('close', function(code) { - t.equal(error, `Cloning into 'doom.git doom1'...\nfatal: unable to access 'http://root:world@localhost:${port}/doom.git doom1/': Empty reply from server\n`); - t.equal(code, 128); - callback(); - }); + }); + } + }); + const port = Math.floor(Math.random() * ((1 << 16) - 1e4)) + 1e4; + repos.listen(port); + + process.chdir(srcDir); + async.waterfall([ + (callback) => { + process.chdir(dstDir); + const clone = spawn('git', ['clone', `http://root:root@localhost:${port}/doom.git`]); + + clone.on('close', function(code) { + t.equal(code, 0); + callback(); + }); + }, + (callback) => { + process.chdir(dstDir); + const clone = spawn('git', ['clone', `http://root:world@localhost:${port}/doom.git doom1`]); + let error = ''; + + clone.stderr.on('data', (d) => { + error += d.toString('utf8'); + }); + + clone.on('close', function(code) { + t.equal(error, `Cloning into 'doom.git doom1'...\nfatal: unable to access 'http://root:world@localhost:${port}/doom.git doom1/': Empty reply from server\n`); + t.equal(code, 128); + callback(); + }); + } + ], (err) => { + t.ok(!err, 'no errors'); + repos.close(); + t.end(); + }); + }); + + t.test('should be able to send custom messages to git client (main stream)', (t) => { + const repoDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; + const srcDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; + const dstDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; + + fs.mkdirSync(repoDir, 0700); + fs.mkdirSync(srcDir, 0700); + fs.mkdirSync(dstDir, 0700); + + const repos = new GitServer(repoDir, { + autoCreate: true + }); + const port = Math.floor(Math.random() * ((1 << 16) - 1e4)) + 1e4; + + repos.on('push', (push) => { + console.log(`push ${push.repo}/${push.commit}`); // eslint-disable-line + push.log(' '); + push.log('Have a great day!'); + push.log(' '); + + push.accept(); + }); + + repos.listen(port); + + process.chdir(srcDir); + + async.waterfall([ + (callback) => { + repos.create('doom', () => { + callback(); + }); + }, + (callback) => { + spawn('git', ['init']) + .on('exit', (code) => { + t.equal(code, 0); + callback(); + }); + }, + (callback) => { + fs.writeFile(srcDir + '/a.txt', 'abcd', () => { + callback(); + }); + }, + (callback) => { + spawn('git', ['add', 'a.txt']) + .on('exit', (code) => { + t.equal(code, 0); + callback(); + }); + }, + (callback) => { + spawn('git', ['commit', '-m', 'a!!']) + .on('exit', () => { + callback(); + }); + }, + (callback) => { + const logs = []; + const push = spawn('git', [ + 'push', 'http://localhost:' + port + '/doom.git', 'master' + ]); + + push.stdout.on('data', (data) => { + if (data.toString() !== '') { + logs.push(data.toString()); } - ], (err) => { - t.ok(!err, 'no errors'); - repos.close(); - t.end(); - }); + }); + + push.stderr.on('data', (data) => { + if (data.toString() !== '') { + logs.push(data.toString()); + } + }); + push.on('exit', () => { + t.ok(logs.join(' ').indexOf('remote: Have a great day!') > -1); + callback(); + }); + }, + ], (err) => { + t.ok(!err, 'no errors'); + repos.close(); + t.end(); + }); }); - t.end(); + t.test('should be able to send custom messages to git client (response stream)', (t) => { + const repoDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; + const srcDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; + const dstDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; + + fs.mkdirSync(repoDir, 0700); + fs.mkdirSync(srcDir, 0700); + fs.mkdirSync(dstDir, 0700); + + const repos = new GitServer(repoDir, { + autoCreate: true + }); + const port = Math.floor(Math.random() * ((1 << 16) - 1e4)) + 1e4; + + repos.on('push', (push) => { + console.log(`push ${push.repo}/${push.commit}`); // eslint-disable-line + + push.on('response', stream => { + stream.log(' '); + stream.log('Have a great day!'); + stream.log(' '); + }); + + push.accept(); + }); + + repos.listen(port); + + process.chdir(srcDir); + + async.waterfall([ + (callback) => { + repos.create('doom', () => { + callback(); + }); + }, + (callback) => { + spawn('git', ['init']) + .on('exit', (code) => { + t.equal(code, 0); + callback(); + }); + }, + (callback) => { + fs.writeFile(srcDir + '/a.txt', 'abcd', () => { + callback(); + }); + }, + (callback) => { + spawn('git', ['add', 'a.txt']) + .on('exit', (code) => { + t.equal(code, 0); + callback(); + }); + }, + (callback) => { + spawn('git', ['commit', '-m', 'a!!']) + .on('exit', () => { + callback(); + }); + }, + (callback) => { + const logs = []; + const push = spawn('git', [ + 'push', 'http://localhost:' + port + '/doom.git', 'master' + ]); + + push.stdout.on('data', (data) => { + if (data.toString() !== '') { + logs.push(data.toString()); + } + }); + + push.stderr.on('data', (data) => { + if (data.toString() !== '') { + logs.push(data.toString()); + } + }); + + push.on('exit', () => { + t.ok(logs.join(' ').indexOf('remote: Have a great day!') > -1); + callback(); + }); + }, + ], (err) => { + t.ok(!err, 'no errors'); + repos.close(); + t.end(); + }); + }); }); From d7840c32f4b337fe98dd6cc5fde4f0ee9419bf4e Mon Sep 17 00:00:00 2001 From: Dan Allen Date: Tue, 4 Dec 2018 09:03:21 -0700 Subject: [PATCH 15/26] add return value to listen method (#44) resolves #43 --- lib/git.js | 2 ++ test/git.js | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/git.js b/lib/git.js index fc91f6e..9ff916c 100644 --- a/lib/git.js +++ b/lib/git.js @@ -433,6 +433,7 @@ class Git extends EventEmitter { * @param {Buffer|String=} options.key - the key file for the https server * @param {Buffer|String=} options.cert - the cert file for the https server * @param {Function} callback - the function to call when server is started or error has occured + * @return {Git} - the Git instance, useful for chaining */ listen(port, options, callback) { const self = this; @@ -447,6 +448,7 @@ class Git extends EventEmitter { }); this.server.listen(port, callback); + return this; } /** * closes the server instance diff --git a/test/git.js b/test/git.js index db300c2..1092db6 100644 --- a/test/git.js +++ b/test/git.js @@ -604,7 +604,7 @@ test('git', (t) => { const repos = new GitServer(repoDir); const port = Math.floor(Math.random() * ((1 << 16) - 1e4)) + 1e4; - repos.listen(port); + t.equal(repos.listen(port), repos); process.chdir(srcDir); async.waterfall([ From 442e48dc555862276dfe042199498eef88f501c1 Mon Sep 17 00:00:00 2001 From: PoLLeN <31079629+pollen5@users.noreply.github.com> Date: Fri, 7 Dec 2018 22:13:04 +0300 Subject: [PATCH 16/26] remove useless 'new' calls (#48) --- lib/service.js | 4 ++-- lib/util.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/service.js b/lib/service.js index e7e93a5..081a8be 100644 --- a/lib/service.js +++ b/lib/service.js @@ -58,7 +58,7 @@ class Service extends HttpDuplex { if(req.headers["authorization"]) { const tokens = req.headers["authorization"].split(" "); if (tokens[0] === "Basic") { - const splitHash = new Buffer.from(tokens[1], 'base64').toString('utf8').split(":"); + const splitHash = Buffer.from(tokens[1], 'base64').toString('utf8').split(":"); this.username = splitHash.shift(); } } @@ -141,7 +141,7 @@ class Service extends HttpDuplex { }; self.emit('response', respStream, function endResponse() { - res.queue(new Buffer.from('0000')); + res.queue(Buffer.from('0000')); res.queue(null); }); diff --git a/lib/util.js b/lib/util.js index 04635a4..3bed53c 100644 --- a/lib/util.js +++ b/lib/util.js @@ -34,7 +34,7 @@ const Util = { } else { const tokens = req.headers["authorization"].split(" "); if (tokens[0] === "Basic") { - const splitHash = new Buffer.from(tokens[1], 'base64').toString('utf8').split(":"); + const splitHash = Buffer.from(tokens[1], 'base64').toString('utf8').split(":"); const username = splitHash.shift(); const password = splitHash.join(":"); callback(username, password, null); From 62b1cb6079cad19b51d4ca7f7d85c48bcc22ed5d Mon Sep 17 00:00:00 2001 From: Dan Allen Date: Sat, 8 Dec 2018 01:43:02 -0700 Subject: [PATCH 17/26] return promise from close method if callback method not specified (#46) resolves #45 --- lib/git.js | 9 +++++++-- test/git.js | 16 +++++++++++++++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/lib/git.js b/lib/git.js index 9ff916c..9f9f129 100644 --- a/lib/git.js +++ b/lib/git.js @@ -454,9 +454,14 @@ class Git extends EventEmitter { * closes the server instance * @method close * @memberof Git + * @param {Promise} - will resolve or reject when the server closes or fails to close. */ - close() { - this.server.close(); + close(callback) { + return new Promise((resolve, reject) => { + this.server.close((err) => { + err ? reject(err) : resolve(); + }); + }); } } diff --git a/test/git.js b/test/git.js index 1092db6..a56330e 100644 --- a/test/git.js +++ b/test/git.js @@ -10,7 +10,7 @@ const async = require('async'); const GitServer = require('../'); test('git', (t) => { - t.plan(8); + t.plan(9); t.test('create, push to, and clone a repo', (t) => { var lastCommit; @@ -623,6 +623,20 @@ test('git', (t) => { }); }); + t.test('should return promise that resolves when server is closed if no callback specified', (t) => { + const repoDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; + + fs.mkdirSync(repoDir, 0700); + + const repos = new GitServer(repoDir); + const port = Math.floor(Math.random() * ((1 << 16) - 1e4)) + 1e4; + repos.listen(port, () => { + repos.close().then(() => { + t.end(); + }); + }); + }); + t.test('should be able to protect certain routes', (t) => { const repoDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; const srcDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; From 70ad208e0fe3f8ffe9832dacae4aaf680000ab5d Mon Sep 17 00:00:00 2001 From: Gabriel Csapo Date: Sun, 3 Mar 2019 14:08:56 -0800 Subject: [PATCH 18/26] 0.5.1 - bump dependencies - tap ^11.0.1 -> ^12.5.3 - tryitout ^2.0.6 -> ^2.1.1 --- CHANGELOG.md | 6 ++++++ package.json | 6 +++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 899d9db..92624bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# 0.5.1 (03/03/2019) + +- bump dependencies + - tap `^11.0.1` -> `^12.5.3` + - tryitout `^2.0.6` -> `^2.1.1` + # 0.5.0 (11/27/2018) - adds `log` functionality for event streams and response streams diff --git a/package.json b/package.json index 339323e..6c5e249 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-git-server", - "version": "0.5.0", + "version": "0.5.1", "description": "🎡 A configurable git server written in Node.js", "author": "Gabriel J. Csapo ", "contributors": [ @@ -36,8 +36,8 @@ "docdash": "^0.4.0", "eslint": "^4.15.0", "jsdoc": "^3.5.5", - "tap": "^11.0.1", + "tap": "^12.5.3", "tape": "^4.8.0", - "tryitout": "^2.0.6" + "tryitout": "^2.1.1" } } From e0bfae6f4e74a87c5c48054fc371631680c45857 Mon Sep 17 00:00:00 2001 From: Gabriel Csapo Date: Tue, 5 Mar 2019 21:10:15 -0800 Subject: [PATCH 19/26] [feature] adds the ability to introspect on the header for the authenticate function (#52) --- CHANGELOG.md | 5 + docs/code/Git.html | 81 +- docs/code/Service.html | 6 +- docs/code/git.js.html | 20 +- docs/code/index.html | 75 +- docs/code/module-lib_util.html | 6 +- docs/code/service.js.html | 65 +- docs/code/util.js.html | 11 +- docs/index.html | 4225 +------------------------------- example/index.js | 4 +- lib/git.js | 13 +- package.json | 2 +- test/git.js | 76 +- 13 files changed, 336 insertions(+), 4253 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92624bc..e19b933 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 0.6.0 (03/03/2019) + +- Augments the authenticate function declaration to accept an object as the first argument and a callback for the second. This allows us to make changes without having to cause breaking changes. + - Adds the ability to introspect on the header (fixes #49) + # 0.5.1 (03/03/2019) - bump dependencies diff --git a/docs/code/Git.html b/docs/code/Git.html index 3e058ee..ec8f958 100644 --- a/docs/code/Git.html +++ b/docs/code/Git.html @@ -271,13 +271,13 @@
                                  Properties
                                  -

                                  a function that has the following arguments (repo, username, password, next) and will be called when a request comes through if set

                                  -
                                   authenticate: (type, repo, username, password, next) => {
                                  +            

                                  a function that has the following arguments ({ type, repo, username, password, headers }, next) and will be called when a request comes through if set

                                  +
                                   authenticate: ({ type, repo, username, password, headers }, next) => {
                                      console.log(type, repo, username, password);
                                      next();
                                    }
                                    // alternatively you can also pass authenticate a promise
                                  - authenticate: (type, repo, username, password, next) => {
                                  + authenticate: ({ type, repo, username, password, headers }, next) => {
                                      console.log(type, repo, username, password);
                                      return new Promise((resolve, reject) => {
                                       if(username === 'foo') {
                                  @@ -393,7 +393,7 @@ 

                                  (static) close<
                                  Source:
                                  @@ -444,6 +444,51 @@

                                  (static) close< + + +

                                  Parameters:
                                  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                                  TypeDescription
                                  + + +Promise + + + +

                                  will resolve or reject when the server closes or fails to close.

                                  @@ -1106,7 +1151,7 @@
                                  Parameters:
                                  -

                                  (static) listen(port, optionsopt, callback)

                                  +

                                  (static) listen(port, optionsopt, callback) → {Git}

                                  @@ -1118,7 +1163,7 @@

                                  (static) liste
                                  Source:
                                  @@ -1435,6 +1480,30 @@

                                  Properties
                                  +
                                  Returns:
                                  + + +
                                  +
                                    +
                                  • the Git instance, useful for chaining
                                  • +
                                  +
                                  + + + +
                                  +
                                  + Type +
                                  +
                                  + +Git + + +
                                  +
                                  + + diff --git a/docs/code/Service.html b/docs/code/Service.html index 2c2603e..4a74a7b 100644 --- a/docs/code/Service.html +++ b/docs/code/Service.html @@ -65,7 +65,7 @@

                                  new ServiceSource:
                                  @@ -1253,7 +1253,7 @@

                                  (static) accep
                                  Source:
                                  @@ -1337,7 +1337,7 @@

                                  (static) rejec
                                  Source:
                                  diff --git a/docs/code/git.js.html b/docs/code/git.js.html index 8435a5f..2229a3a 100644 --- a/docs/code/git.js.html +++ b/docs/code/git.js.html @@ -157,14 +157,14 @@

                                  git.js

                                  * @param {Object} options - options that can be applied on the new instance being created * @param {Boolean=} options.autoCreate - By default, repository targets will be created if they don't exist. You can disable that behavior with `options.autoCreate = true` - * @param {Function} options.authenticate - a function that has the following arguments (repo, username, password, next) and will be called when a request comes through if set + * @param {Function} options.authenticate - a function that has the following arguments ({ type, repo, username, password, headers }, next) and will be called when a request comes through if set * - authenticate: (type, repo, username, password, next) => { + authenticate: ({ type, repo, username, password, headers }, next) => { console.log(type, repo, username, password); next(); } // alternatively you can also pass authenticate a promise - authenticate: (type, repo, username, password, next) => { + authenticate: ({ type, repo, username, password, headers }, next) => { console.log(type, repo, username, password); return new Promise((resolve, reject) => { if(username === 'foo') { @@ -341,9 +341,12 @@

                                  git.js

                                  // check if the repo is authenticated if(this.authenticate) { const type = this.getType(service); - const promise = this.authenticate(type, repoName, basicAuth.bind(null, req, res), (error) => { + const headers = req.headers; + const user = basicAuth.bind(null, req, res); + const promise = this.authenticate({ type, repo: repoName, user, headers }, (error) => { return next(error); }); + if(promise instanceof Promise) { return promise .then(next) @@ -472,6 +475,7 @@

                                  git.js

                                  * @param {Buffer|String=} options.key - the key file for the https server * @param {Buffer|String=} options.cert - the cert file for the https server * @param {Function} callback - the function to call when server is started or error has occured + * @return {Git} - the Git instance, useful for chaining */ listen(port, options, callback) { const self = this; @@ -486,14 +490,20 @@

                                  git.js

                                  }); this.server.listen(port, callback); + return this; } /** * closes the server instance * @method close * @memberof Git + * @param {Promise} - will resolve or reject when the server closes or fails to close. */ close() { - this.server.close(); + return new Promise((resolve, reject) => { + this.server.close((err) => { + err ? reject(err) : resolve(); + }); + }); } } diff --git a/docs/code/index.html b/docs/code/index.html index d1181b8..06af68b 100644 --- a/docs/code/index.html +++ b/docs/code/index.html @@ -48,6 +48,8 @@

                                  Home

                                  Classes

                                  • node-git-server

                                    🎡 A configurable git server written in Node.js

                                    +
                                    +

                                    there be 🐲 here! The API's and functionality are still be cemented, anything before a 1.0.0 release will be subject to change.

                                    @@ -59,7 +61,76 @@

                                    Home

                                    Classes

                                    • devDependency Status npm npm

                                      -

                                      Install

                                      npm install node-git-server

                                      Usage

                                      const path = require('path');
                                      +

                                      Install

                                      npm install node-git-server

                                      Usage

                                      Simple

                                      const path = require('path');
                                      +const Server = require('node-git-server');
                                      +
                                      +const repos = new Server(path.resolve(__dirname, 'tmp'), {
                                      +    autoCreate: true
                                      +});
                                      +const port = process.env.PORT || 7005;
                                      +
                                      +repos.on('push', (push) => {
                                      +    console.log(`push ${push.repo}/${push.commit} (${push.branch})`);
                                      +    push.accept();
                                      +});
                                      +
                                      +repos.on('fetch', (fetch) => {
                                      +    console.log(`fetch ${fetch.commit}`);
                                      +    fetch.accept();
                                      +});
                                      +
                                      +repos.listen(port, () => {
                                      +    console.log(`node-git-server running at http://localhost:${port}`)
                                      +});

                                      then start up the node-git-server server...

                                      +
                                      $ node example/index.js

                                      meanwhile...

                                      +
                                      $ git push http://localhost:7005/beep master
                                      +Counting objects: 356, done.
                                      +Delta compression using up to 2 threads.
                                      +Compressing objects: 100% (133/133), done.
                                      +Writing objects: 100% (356/356), 46.20 KiB, done.
                                      +Total 356 (delta 210), reused 355 (delta 210)
                                      +To http://localhost:7005/beep
                                      + * [new branch]      master -> master

                                      Sending logs

                                      const path = require('path');
                                      +const Server = require('node-git-server');
                                      +
                                      +const repos = new Server(path.resolve(__dirname, 'tmp'), {
                                      +    autoCreate: true
                                      +});
                                      +const port = process.env.PORT || 7005;
                                      +
                                      +repos.on('push', (push) => {
                                      +    console.log(`push ${push.repo}/${push.commit} (${push.branch})`);
                                      +
                                      +    repos.list((err, results) => {
                                      +        push.log(' ');
                                      +        push.log('Hey!');
                                      +        push.log('Checkout these other repos:');
                                      +        for(const repo of results) {
                                      +          push.log(`- ${repo}`);
                                      +        }
                                      +        push.log(' ');
                                      +    });
                                      +
                                      +    push.accept();
                                      +});
                                      +
                                      +repos.listen(port, () => {
                                      +    console.log(`node-git-server running at http://localhost:${port}`)
                                      +});

                                      then start up the node-git-server server...

                                      +
                                      $ node example/index.js

                                      meanwhile...

                                      +
                                      $ git push http://localhost:7005/beep master
                                      +Counting objects: 356, done.
                                      +Delta compression using up to 2 threads.
                                      +Compressing objects: 100% (133/133), done.
                                      +Writing objects: 100% (356/356), 46.20 KiB, done.
                                      +Total 356 (delta 210), reused 355 (delta 210)
                                      +remote:  
                                      +remote: Hey!
                                      +remote: Checkout these other repos:
                                      +remote: - test.git
                                      +remote:  
                                      +To http://localhost:7005/test
                                      +   77bb26e..22918d5  master -> master

                                      Authentication

                                      const path = require('path');
                                       const Server = require('node-git-server');
                                       
                                       const repos = new Server(path.resolve(__dirname, 'tmp'), {
                                      @@ -98,7 +169,7 @@ 

                                      Install

                                      npm install node-git-serve
                                       Writing objects: 100% (356/356), 46.20 KiB, done.
                                       Total 356 (delta 210), reused 355 (delta 210)
                                       To http://localhost:7005/beep
                                      - * [new branch]      master -> master

                                      Example

                                      Running the following command will start up a simple http server:

                                      + * [new branch] master -> master

                                      Example

                                      Running the following command will start up a simple http server:

                                      node example/index.js

                                      If you want to try using https run the following

                                      node example/index.js --https

                                      When running https with self-signed certs there are two ways to override the git-clients behavior using git config http.sslVerify false or git config --global http.sslCAInfo /path/to/cert.pem

                                      diff --git a/docs/code/module-lib_util.html b/docs/code/module-lib_util.html index 19d87f7..332fccc 100644 --- a/docs/code/module-lib_util.html +++ b/docs/code/module-lib_util.html @@ -263,7 +263,7 @@

                                      (inner)
                                      Source:
                                      @@ -460,7 +460,7 @@

                                      (inner)
                                      Source:
                                      @@ -974,7 +974,7 @@

                                      (inner)
                                      Source:
                                      diff --git a/docs/code/service.js.html b/docs/code/service.js.html index f6073fd..c53319c 100644 --- a/docs/code/service.js.html +++ b/docs/code/service.js.html @@ -38,16 +38,23 @@

                                      service.js

                                      const through = require('through');
                                      -const HttpDuplex = require('./http-duplex');
                                       const zlib = require('zlib');
                                      -
                                      +const util = require('util');
                                      +const os = require('os');
                                       const { spawn } = require('child_process');
                                       
                                      +const HttpDuplex = require('./http-duplex');
                                      +
                                       const headerRE = {
                                         'receive-pack': '([0-9a-fA-F]+) ([0-9a-fA-F]+) refs\/(heads|tags)\/(.*?)( |00|\u0000)|^(0000)$', // eslint-disable-line
                                         'upload-pack': '^\\S+ ([0-9a-fA-F]+)'
                                       };
                                       
                                      +const packSideband = s => {
                                      +  const n = (4 + s.length).toString(16);
                                      +  return Array(4 - n.length + 1).join('0') + n + s;
                                      +};
                                      +
                                       class Service extends HttpDuplex {
                                         /**
                                          * Handles invoking the git-*-pack binaries
                                      @@ -67,6 +74,7 @@ 

                                      service.js

                                      this.repo = opts.repo; this.service = opts.service; this.cwd = opts.cwd; + this.logs = []; var buffered = through().pause(); @@ -89,7 +97,7 @@

                                      service.js

                                      if(req.headers["authorization"]) { const tokens = req.headers["authorization"].split(" "); if (tokens[0] === "Basic") { - const splitHash = new Buffer(tokens[1], 'base64').toString('utf8').split(":"); + const splitHash = Buffer.from(tokens[1], 'base64').toString('utf8').split(":"); this.username = splitHash.shift(); } } @@ -135,35 +143,63 @@

                                      service.js

                                      self.once('accept', function onAccept() { process.nextTick(function() { - var cmd = ['git-' + opts.service, '--stateless-rpc', opts.cwd]; - var ps = spawn(cmd[0], cmd.slice(1)); + const cmd = os.platform() == 'win32' ? + ['git', opts.service, '--stateless-rpc', opts.cwd] + : + ['git-' + opts.service, '--stateless-rpc', opts.cwd]; + + const ps = spawn(cmd[0], cmd.slice(1)); + ps.on('error', function(err) { self.emit('error', new Error(`${err.message} running command ${cmd.join(' ')}`)); }); self.emit('service', ps); - var respStream = through(function(c) { - if (self.listeners('response').length === 0) return this.queue(c); + var respStream = through(function write(c) { + if (self.listeners('response').length === 0) { + if(self.logs.length > 0) { + while(self.logs.length > 0) { + this.queue(self.logs.pop()); + } + } + + return this.queue(c); + } // prevent git from sending the close signal if (c.length === 4 && c.toString() === '0000') return; this.queue(c); - }, function() { + }, function end() { if (self.listeners('response').length > 0) return; + this.queue(null); }); + respStream.log = function() { + self.log(...arguments); + }; self.emit('response', respStream, function endResponse() { - res.queue(new Buffer('0000')); + res.queue(Buffer.from('0000')); res.queue(null); }); + ps.stdout.pipe(respStream).pipe(res); buffered.pipe(ps.stdin); buffered.resume(); - ps.on('exit', self.emit.bind(self, 'exit')); + ps.on('exit', () => { + if(self.logs.length > 0) { + while(self.logs.length > 0) { + respStream.queue(self.logs.pop()); + } + respStream.queue(Buffer.from('0000')); + respStream.queue(null); + } + + self.emit.bind(self, 'exit'); + }); }); }); @@ -172,6 +208,15 @@

                                      service.js

                                      res.end(msg); }); } + + log() { + const _log = util.format(...arguments); + const SIDEBAND = String.fromCharCode(2); // PROGRESS + const message = `${SIDEBAND}${_log}\n`; + const formattedMessage = Buffer.from(packSideband(message)); + + this.logs.unshift(formattedMessage); + } /** * reject request in flight * @method reject diff --git a/docs/code/util.js.html b/docs/code/util.js.html index 458281a..c874c4f 100644 --- a/docs/code/util.js.html +++ b/docs/code/util.js.html @@ -73,7 +73,7 @@

                                      util.js

                                      } else { const tokens = req.headers["authorization"].split(" "); if (tokens[0] === "Basic") { - const splitHash = new Buffer(tokens[1], 'base64').toString('utf8').split(":"); + const splitHash = Buffer.from(tokens[1], 'base64').toString('utf8').split(":"); const username = splitHash.shift(); const password = splitHash.join(":"); callback(username, password, null); @@ -123,7 +123,14 @@

                                      util.js

                                      res.write(pack('# service=git-' + service + '\n')); res.write('0000'); - const cmd = ['git-' + service, '--stateless-rpc', '--advertise-refs', repoLocation]; + var cmd = null; + var isWin = /^win/.test(process.platform); + if (isWin) { + cmd = ['git', service, '--stateless-rpc', '--advertise-refs', repoLocation]; + } else { + cmd = ['git-' + service, '--stateless-rpc', '--advertise-refs', repoLocation]; + } + const ps = spawn(cmd[0], cmd.slice(1)); ps.on('error', (err) => { dup.emit('error', new Error(`${err.message} running command ${cmd.join(' ')}`)); diff --git a/docs/index.html b/docs/index.html index be82179..23abc03 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1,11 +1,4 @@ - - - - - - - - - -
                                      -
                                      - +Object.defineProperty(t,"__esModule",{value:!0});var n=null,r=!1,o=3,i=-1,a=-1,l=!1,u=!1;function c(){if(!l){var e=n.expirationTime;u?_():u=!0,w(f,e)}}function s(){var e=n,t=n.next;if(n===t)n=null;else{var r=n.previous;n=r.next=t,t.previous=r}e.next=e.previous=null,r=e.callback,t=e.expirationTime,e=e.priorityLevel;var i=o,l=a;o=e,a=t;try{var u=r()}finally{o=i,a=l}if("function"==typeof u)if(u={callback:u,priorityLevel:e,expirationTime:t,next:null,previous:null},null===n)n=u.next=u.previous=u;else{r=null,e=n;do{if(e.expirationTime>=t){r=e;break}e=e.next}while(e!==n);null===r?r=n:r===n&&(n=u,c()),(t=r.previous).next=r.previous=u,u.next=r,u.previous=t}}function d(){if(-1===i&&null!==n&&1===n.priorityLevel){l=!0;try{do{s()}while(null!==n&&1===n.priorityLevel)}finally{l=!1,null!==n?c():u=!1}}}function f(e){l=!0;var o=r;r=e;try{if(e)for(;null!==n;){var i=t.unstable_now();if(!(n.expirationTime<=i))break;do{s()}while(null!==n&&n.expirationTime<=i)}else if(null!==n)do{s()}while(null!==n&&!T())}finally{l=!1,r=o,null!==n?c():u=!1,d()}}var p,h,g=Date,m="function"==typeof setTimeout?setTimeout:void 0,b="function"==typeof clearTimeout?clearTimeout:void 0,v="function"==typeof requestAnimationFrame?requestAnimationFrame:void 0,y="function"==typeof cancelAnimationFrame?cancelAnimationFrame:void 0;function x(e){p=v(function(t){b(h),e(t)}),h=m(function(){y(p),e(t.unstable_now())},100)}if("object"==typeof performance&&"function"==typeof performance.now){var k=performance;t.unstable_now=function(){return k.now()}}else t.unstable_now=function(){return g.now()};var w,_,T,S=null;if("undefined"!=typeof window?S=window:void 0!==e&&(S=e),S&&S._schedMock){var E=S._schedMock;w=E[0],_=E[1],T=E[2],t.unstable_now=E[3]}else if("undefined"==typeof window||"function"!=typeof MessageChannel){var C=null,P=function(e){if(null!==C)try{C(e)}finally{C=null}};w=function(e){null!==C?setTimeout(w,0,e):(C=e,setTimeout(P,0,!1))},_=function(){C=null},T=function(){return!1}}else{"undefined"!=typeof console&&("function"!=typeof v&&console.error("This browser doesn't support requestAnimationFrame. Make sure that you load a polyfill in older browsers. https://fb.me/react-polyfills"),"function"!=typeof y&&console.error("This browser doesn't support cancelAnimationFrame. Make sure that you load a polyfill in older browsers. https://fb.me/react-polyfills"));var N=null,z=!1,R=-1,O=!1,M=!1,I=0,L=33,U=33;T=function(){return I<=t.unstable_now()};var A=new MessageChannel,D=A.port2;A.port1.onmessage=function(){z=!1;var e=N,n=R;N=null,R=-1;var r=t.unstable_now(),o=!1;if(0>=I-r){if(!(-1!==n&&n<=r))return O||(O=!0,x(F)),N=e,void(R=n);o=!0}if(null!==e){M=!0;try{e(o)}finally{M=!1}}};var F=function(e){if(null!==N){x(F);var t=e-I+U;tt&&(t=8),U=tt?D.postMessage(void 0):O||(O=!0,x(F))},_=function(){N=null,z=!1,R=-1}}t.unstable_ImmediatePriority=1,t.unstable_UserBlockingPriority=2,t.unstable_NormalPriority=3,t.unstable_IdlePriority=5,t.unstable_LowPriority=4,t.unstable_runWithPriority=function(e,n){switch(e){case 1:case 2:case 3:case 4:case 5:break;default:e=3}var r=o,a=i;o=e,i=t.unstable_now();try{return n()}finally{o=r,i=a,d()}},t.unstable_next=function(e){switch(o){case 1:case 2:case 3:var n=3;break;default:n=o}var r=o,a=i;o=n,i=t.unstable_now();try{return e()}finally{o=r,i=a,d()}},t.unstable_scheduleCallback=function(e,r){var a=-1!==i?i:t.unstable_now();if("object"==typeof r&&null!==r&&"number"==typeof r.timeout)r=a+r.timeout;else switch(o){case 1:r=a+-1;break;case 2:r=a+250;break;case 5:r=a+1073741823;break;case 4:r=a+1e4;break;default:r=a+5e3}if(e={callback:e,priorityLevel:o,expirationTime:r,next:null,previous:null},null===n)n=e.next=e.previous=e,c();else{a=null;var l=n;do{if(l.expirationTime>r){a=l;break}l=l.next}while(l!==n);null===a?a=n:a===n&&(n=e,c()),(r=a.previous).next=a.previous=e,e.next=a,e.previous=r}return e},t.unstable_cancelCallback=function(e){var t=e.next;if(null!==t){if(t===e)n=null;else{e===n&&(n=t);var r=e.previous;r.next=t,t.previous=r}e.next=e.previous=null}},t.unstable_wrapCallback=function(e){var n=o;return function(){var r=o,a=i;o=n,i=t.unstable_now();try{return e.apply(this,arguments)}finally{o=r,i=a,d()}}},t.unstable_getCurrentPriorityLevel=function(){return o},t.unstable_shouldYield=function(){return!r&&(null!==n&&n.expirationTime1)for(var n=1;n ?(paragraph|[^\n]*)(?:\n|$))+/,list:/^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,html:"^ {0,3}(?:<(script|pre|style)[\\s>][\\s\\S]*?(?:[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?\\?>\\n*|\\n*|\\n*|)[\\s\\S]*?(?:\\n{2,}|$)|<(?!script|pre|style)([a-z][\\w-]*)(?:attribute)*? */?>(?=\\h*\\n)[\\s\\S]*?(?:\\n{2,}|$)|(?=\\h*\\n)[\\s\\S]*?(?:\\n{2,}|$))",def:/^ {0,3}\[(label)\]: *\n? *]+)>?(?:(?: +\n? *| *\n *)(title))? *(?:\n+|$)/,table:g,lheading:/^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/,paragraph:/^([^\n]+(?:\n(?!hr|heading|lheading| {0,3}>|<\/?(?:tag)(?: +|\n|\/?>)|<(?:script|pre|style|!--))[^\n]+)*)/,text:/^[^\n]+/};function r(e){this.tokens=[],this.tokens.links=Object.create(null),this.options=e||y.defaults,this.rules=n.normal,this.options.pedantic?this.rules=n.pedantic:this.options.gfm&&(this.options.tables?this.rules=n.tables:this.rules=n.gfm)}n._label=/(?!\s*\])(?:\\[\[\]]|[^\[\]])+/,n._title=/(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/,n.def=d(n.def).replace("label",n._label).replace("title",n._title).getRegex(),n.bullet=/(?:[*+-]|\d+\.)/,n.item=/^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/,n.item=d(n.item,"gm").replace(/bull/g,n.bullet).getRegex(),n.list=d(n.list).replace(/bull/g,n.bullet).replace("hr","\\n+(?=\\1?(?:(?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$))").replace("def","\\n+(?="+n.def.source+")").getRegex(),n._tag="address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul",n._comment=//,n.html=d(n.html,"i").replace("comment",n._comment).replace("tag",n._tag).replace("attribute",/ +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex(),n.paragraph=d(n.paragraph).replace("hr",n.hr).replace("heading",n.heading).replace("lheading",n.lheading).replace("tag",n._tag).getRegex(),n.blockquote=d(n.blockquote).replace("paragraph",n.paragraph).getRegex(),n.normal=m({},n),n.gfm=m({},n.normal,{fences:/^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\n? *\1 *(?:\n+|$)/,paragraph:/^/,heading:/^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/}),n.gfm.paragraph=d(n.paragraph).replace("(?!","(?!"+n.gfm.fences.source.replace("\\1","\\2")+"|"+n.list.source.replace("\\1","\\3")+"|").getRegex(),n.tables=m({},n.gfm,{nptable:/^ *([^|\n ].*\|.*)\n *([-:]+ *\|[-| :]*)(?:\n((?:.*[^>\n ].*(?:\n|$))*)\n*|$)/,table:/^ *\|(.+)\n *\|?( *[-:]+[-| :]*)(?:\n((?: *[^>\n ].*(?:\n|$))*)\n*|$)/}),n.pedantic=m({},n.normal,{html:d("^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+? *(?:\\n{2,}|\\s*$)|\\s]*)*?/?> *(?:\\n{2,}|\\s*$))").replace("comment",n._comment).replace(/tag/g,"(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(),def:/^ *\[([^\]]+)\]: *]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/}),r.rules=n,r.lex=function(e,t){return new r(t).lex(e)},r.prototype.lex=function(e){return e=e.replace(/\r\n|\r/g,"\n").replace(/\t/g," ").replace(/\u00a0/g," ").replace(/\u2424/g,"\n"),this.token(e,!0)},r.prototype.token=function(e,t){var r,o,i,a,l,u,c,s,d,f,p,h,g,m,y,x;for(e=e.replace(/^ +$/gm,"");e;)if((i=this.rules.newline.exec(e))&&(e=e.substring(i[0].length),i[0].length>1&&this.tokens.push({type:"space"})),i=this.rules.code.exec(e))e=e.substring(i[0].length),i=i[0].replace(/^ {4}/gm,""),this.tokens.push({type:"code",text:this.options.pedantic?i:v(i,"\n")});else if(i=this.rules.fences.exec(e))e=e.substring(i[0].length),this.tokens.push({type:"code",lang:i[2],text:i[3]||""});else if(i=this.rules.heading.exec(e))e=e.substring(i[0].length),this.tokens.push({type:"heading",depth:i[1].length,text:i[2]});else if(t&&(i=this.rules.nptable.exec(e))&&(u={type:"table",header:b(i[1].replace(/^ *| *\| *$/g,"")),align:i[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:i[3]?i[3].replace(/\n$/,"").split("\n"):[]}).header.length===u.align.length){for(e=e.substring(i[0].length),p=0;p ?/gm,""),this.token(i,t),this.tokens.push({type:"blockquote_end"});else if(i=this.rules.list.exec(e)){for(e=e.substring(i[0].length),c={type:"list_start",ordered:m=(a=i[2]).length>1,start:m?+a:"",loose:!1},this.tokens.push(c),s=[],r=!1,g=(i=i[0].match(this.rules.item)).length,p=0;p1&&l.length>1||(e=i.slice(p+1).join("\n")+e,p=g-1)),o=r||/\n\n(?!\s*$)/.test(u),p!==g-1&&(r="\n"===u.charAt(u.length-1),o||(o=r)),o&&(c.loose=!0),x=void 0,(y=/^\[[ xX]\] /.test(u))&&(x=" "!==u[1],u=u.replace(/^\[[ xX]\] +/,"")),d={type:"list_item_start",task:y,checked:x,loose:o},s.push(d),this.tokens.push(d),this.token(u,!1),this.tokens.push({type:"list_item_end"});if(c.loose)for(g=s.length,p=0;p?@\[\]\\^_`{|}~])/,autolink:/^<(scheme:[^\s\x00-\x1f<>]*|email)>/,url:g,tag:"^comment|^|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^|^",link:/^!?\[(label)\]\(href(?:\s+(title))?\s*\)/,reflink:/^!?\[(label)\]\[(?!\s*\])((?:\\[\[\]]?|[^\[\]\\])+)\]/,nolink:/^!?\[(?!\s*\])((?:\[[^\[\]]*\]|\\[\[\]]|[^\[\]])*)\](?:\[\])?/,strong:/^__([^\s])__(?!_)|^\*\*([^\s])\*\*(?!\*)|^__([^\s][\s\S]*?[^\s])__(?!_)|^\*\*([^\s][\s\S]*?[^\s])\*\*(?!\*)/,em:/^_([^\s_])_(?!_)|^\*([^\s*"<\[])\*(?!\*)|^_([^\s][\s\S]*?[^\s_])_(?!_|[^\s.])|^_([^\s_][\s\S]*?[^\s])_(?!_|[^\s.])|^\*([^\s"<\[][\s\S]*?[^\s*])\*(?!\*)|^\*([^\s*"<\[][\s\S]*?[^\s])\*(?!\*)/,code:/^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/,br:/^( {2,}|\\)\n(?!\s*$)/,del:g,text:/^(`+|[^`])[\s\S]*?(?=[\\?@\[\]\\^_`{|}~])/g,o._scheme=/[a-zA-Z][a-zA-Z0-9+.-]{1,31}/,o._email=/[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/,o.autolink=d(o.autolink).replace("scheme",o._scheme).replace("email",o._email).getRegex(),o._attribute=/\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/,o.tag=d(o.tag).replace("comment",n._comment).replace("attribute",o._attribute).getRegex(),o._label=/(?:\[[^\[\]]*\]|\\[\[\]]?|`[^`]*`|[^\[\]\\])*?/,o._href=/\s*(<(?:\\[<>]?|[^\s<>\\])*>|(?:\\[()]?|\([^\s\x00-\x1f\\]*\)|[^\s\x00-\x1f()\\])*?)/,o._title=/"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/,o.link=d(o.link).replace("label",o._label).replace("href",o._href).replace("title",o._title).getRegex(),o.reflink=d(o.reflink).replace("label",o._label).getRegex(),o.normal=m({},o),o.pedantic=m({},o.normal,{strong:/^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,em:/^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/,link:d(/^!?\[(label)\]\((.*?)\)/).replace("label",o._label).getRegex(),reflink:d(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label",o._label).getRegex()}),o.gfm=m({},o.normal,{escape:d(o.escape).replace("])","~|])").getRegex(),_extended_email:/[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/,url:/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/,_backpedal:/(?:[^?!.,:;*_~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_~)]+(?!$))+/,del:/^~+(?=\S)([\s\S]*?\S)~+/,text:d(o.text).replace("]|","~]|").replace("|$","|https?://|ftp://|www\\.|[a-zA-Z0-9.!#$%&'*+/=?^_`{\\|}~-]+@|$").getRegex()}),o.gfm.url=d(o.gfm.url).replace("email",o.gfm._extended_email).getRegex(),o.breaks=m({},o.gfm,{br:d(o.br).replace("{2,}","*").getRegex(),text:d(o.gfm.text).replace("{2,}","*").getRegex()}),i.rules=o,i.output=function(e,t,n){return new i(t,n).output(e)},i.prototype.output=function(e){for(var t,n,r,o,a,l,u="";e;)if(a=this.rules.escape.exec(e))e=e.substring(a[0].length),u+=a[1];else if(a=this.rules.autolink.exec(e))e=e.substring(a[0].length),r="@"===a[2]?"mailto:"+(n=c(this.mangle(a[1]))):n=c(a[1]),u+=this.renderer.link(r,null,n);else if(this.inLink||!(a=this.rules.url.exec(e))){if(a=this.rules.tag.exec(e))!this.inLink&&/^/i.test(a[0])&&(this.inLink=!1),!this.inRawBlock&&/^<(pre|code|kbd|script)(\s|>)/i.test(a[0])?this.inRawBlock=!0:this.inRawBlock&&/^<\/(pre|code|kbd|script)(\s|>)/i.test(a[0])&&(this.inRawBlock=!1),e=e.substring(a[0].length),u+=this.options.sanitize?this.options.sanitizer?this.options.sanitizer(a[0]):c(a[0]):a[0];else if(a=this.rules.link.exec(e))e=e.substring(a[0].length),this.inLink=!0,r=a[2],this.options.pedantic?(t=/^([^'"]*[^\s])\s+(['"])(.*)\2/.exec(r))?(r=t[1],o=t[3]):o="":o=a[3]?a[3].slice(1,-1):"",r=r.trim().replace(/^<([\s\S]*)>$/,"$1"),u+=this.outputLink(a,{href:i.escapes(r),title:i.escapes(o)}),this.inLink=!1;else if((a=this.rules.reflink.exec(e))||(a=this.rules.nolink.exec(e))){if(e=e.substring(a[0].length),t=(a[2]||a[1]).replace(/\s+/g," "),!(t=this.links[t.toLowerCase()])||!t.href){u+=a[0].charAt(0),e=a[0].substring(1)+e;continue}this.inLink=!0,u+=this.outputLink(a,t),this.inLink=!1}else if(a=this.rules.strong.exec(e))e=e.substring(a[0].length),u+=this.renderer.strong(this.output(a[4]||a[3]||a[2]||a[1]));else if(a=this.rules.em.exec(e))e=e.substring(a[0].length),u+=this.renderer.em(this.output(a[6]||a[5]||a[4]||a[3]||a[2]||a[1]));else if(a=this.rules.code.exec(e))e=e.substring(a[0].length),u+=this.renderer.codespan(c(a[2].trim(),!0));else if(a=this.rules.br.exec(e))e=e.substring(a[0].length),u+=this.renderer.br();else if(a=this.rules.del.exec(e))e=e.substring(a[0].length),u+=this.renderer.del(this.output(a[1]));else if(a=this.rules.text.exec(e))e=e.substring(a[0].length),this.inRawBlock?u+=this.renderer.text(a[0]):u+=this.renderer.text(c(this.smartypants(a[0])));else if(e)throw new Error("Infinite loop on byte: "+e.charCodeAt(0))}else{if("@"===a[2])r="mailto:"+(n=c(a[0]));else{do{l=a[0],a[0]=this.rules._backpedal.exec(a[0])[0]}while(l!==a[0]);n=c(a[0]),r="www."===a[1]?"http://"+n:n}e=e.substring(a[0].length),u+=this.renderer.link(r,null,n)}return u},i.escapes=function(e){return e?e.replace(i.rules._escapes,"$1"):e},i.prototype.outputLink=function(e,t){var n=t.href,r=t.title?c(t.title):null;return"!"!==e[0].charAt(0)?this.renderer.link(n,r,this.output(e[1])):this.renderer.image(n,r,c(e[1]))},i.prototype.smartypants=function(e){return this.options.smartypants?e.replace(/---/g,"—").replace(/--/g,"–").replace(/(^|[-\u2014\/(\[{"\s])'/g,"$1‘").replace(/'/g,"’").replace(/(^|[-\u2014\/(\[{\u2018\s])"/g,"$1“").replace(/"/g,"”").replace(/\.{3}/g,"…"):e},i.prototype.mangle=function(e){if(!this.options.mangle)return e;for(var t,n="",r=e.length,o=0;o.5&&(t="x"+t.toString(16)),n+="&#"+t+";";return n},a.prototype.code=function(e,t,n){if(this.options.highlight){var r=this.options.highlight(e,t);null!=r&&r!==e&&(n=!0,e=r)}return t?'
                                      '+(n?e:c(e,!0))+"
                                      \n":"
                                      "+(n?e:c(e,!0))+"
                                      "},a.prototype.blockquote=function(e){return"
                                      \n"+e+"
                                      \n"},a.prototype.html=function(e){return e},a.prototype.heading=function(e,t,n){return this.options.headerIds?"'+e+"\n":""+e+"\n"},a.prototype.hr=function(){return this.options.xhtml?"
                                      \n":"
                                      \n"},a.prototype.list=function(e,t,n){var r=t?"ol":"ul";return"<"+r+(t&&1!==n?' start="'+n+'"':"")+">\n"+e+"\n"},a.prototype.listitem=function(e){return"
                                    • "+e+"
                                    • \n"},a.prototype.checkbox=function(e){return" "},a.prototype.paragraph=function(e){return"

                                      "+e+"

                                      \n"},a.prototype.table=function(e,t){return t&&(t=""+t+""),"\n\n"+e+"\n"+t+"
                                      \n"},a.prototype.tablerow=function(e){return"\n"+e+"\n"},a.prototype.tablecell=function(e,t){var n=t.header?"th":"td";return(t.align?"<"+n+' align="'+t.align+'">':"<"+n+">")+e+"\n"},a.prototype.strong=function(e){return""+e+""},a.prototype.em=function(e){return""+e+""},a.prototype.codespan=function(e){return""+e+""},a.prototype.br=function(){return this.options.xhtml?"
                                      ":"
                                      "},a.prototype.del=function(e){return""+e+""},a.prototype.link=function(e,t,n){if(null===(e=f(this.options.sanitize,this.options.baseUrl,e)))return n;var r='
                                      "},a.prototype.image=function(e,t,n){if(null===(e=f(this.options.sanitize,this.options.baseUrl,e)))return n;var r=''+n+'":">"},a.prototype.text=function(e){return e},l.prototype.strong=l.prototype.em=l.prototype.codespan=l.prototype.del=l.prototype.text=function(e){return e},l.prototype.link=l.prototype.image=function(e,t,n){return""+n},l.prototype.br=function(){return""},u.parse=function(e,t){return new u(t).parse(e)},u.prototype.parse=function(e){this.inline=new i(e.links,this.options),this.inlineText=new i(e.links,m({},this.options,{renderer:new l})),this.tokens=e.reverse();for(var t="";this.next();)t+=this.tok();return t},u.prototype.next=function(){return this.token=this.tokens.pop()},u.prototype.peek=function(){return this.tokens[this.tokens.length-1]||0},u.prototype.parseText=function(){for(var e=this.token.text;"text"===this.peek().type;)e+="\n"+this.next().text;return this.inline.output(e)},u.prototype.tok=function(){switch(this.token.type){case"space":return"";case"hr":return this.renderer.hr();case"heading":return this.renderer.heading(this.inline.output(this.token.text),this.token.depth,s(this.inlineText.output(this.token.text)));case"code":return this.renderer.code(this.token.text,this.token.lang,this.token.escaped);case"table":var e,t,n,r,o="",i="";for(n="",e=0;e"']/,c.escapeReplace=/[&<>"']/g,c.replacements={"&":"&","<":"<",">":">",'"':""","'":"'"},c.escapeTestNoEncode=/[<>"']|&(?!#?\w+;)/,c.escapeReplaceNoEncode=/[<>"']|&(?!#?\w+;)/g;var p={},h=/^$|^[a-z][a-z0-9+.-]*:|^[?#]/i;function g(){}function m(e){for(var t,n,r=1;r=0&&"\\"===n[o];)r=!r;return r?"|":" |"}).split(/ \|/),r=0;if(n.length>t)n.splice(t);else for(;n.lengthAn error occurred:

                                      "+c(e.message+"",!0)+"
                                      ";throw e}}g.exec=g,y.options=y.setOptions=function(e){return m(y.defaults,e),y},y.getDefaults=function(){return{baseUrl:null,breaks:!1,gfm:!0,headerIds:!0,headerPrefix:"",highlight:null,langPrefix:"language-",mangle:!0,pedantic:!1,renderer:new a,sanitize:!1,sanitizer:null,silent:!1,smartLists:!1,smartypants:!1,tables:!0,xhtml:!1}},y.defaults=y.getDefaults(),y.Parser=u,y.parser=u.parse,y.Renderer=a,y.TextRenderer=l,y.Lexer=r,y.lexer=r.lex,y.InlineLexer=i,y.inlineLexer=i.output,y.parse=y,e.exports=y}(this||"undefined"!=typeof window&&window)}).call(this,n(6))},function(module,exports,__webpack_require__){"use strict";__webpack_require__(27),__webpack_require__(29);var _krayon2=_interopRequireDefault(__webpack_require__(31)),_react=_interopRequireDefault(__webpack_require__(0)),_propTypes=_interopRequireDefault(__webpack_require__(1)),_textarea=_interopRequireDefault(__webpack_require__(33)),_util=__webpack_require__(4);function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}class Editor extends _react.default.Component{constructor(e){super(e),this.state={value:(0,_util.cleanString)(e.value),output:{},duration:0}}run(){const self=this,{value:value}=this.state;return function(){var c={cons:[],log:function(){c.cons.push({type:"text",value:Array.prototype.join.call(arguments," ")}),self.setState({output:c})},html:function(e){c.cons.push({type:"html",value:e}),self.setState({output:c})}};const start=Date.now();try{!function(console){c.val=eval(value)}(c)}catch(e){c.log("Error:",e)}self.setState({output:c,duration:Date.now()-start})}(),this}onChange(e){this.setState({value:e})}render(){const{title:e,subtitle:t}=this.props,{value:n,output:r,duration:o}=this.state,{cons:i,val:a}=r;let l=(0,_krayon2.default)(n.replace(//g,">"));return _react.default.createElement("div",{className:"editor"},_react.default.createElement("div",{className:"text-left text-black"},(0,_util.cleanString)(e),t?_react.default.createElement("div",null,_react.default.createElement("small",null," ",(0,_util.cleanString)(t)," ")):""),_react.default.createElement("br",null),_react.default.createElement("div",{style:{position:"relative"}},_react.default.createElement(_textarea.default,{onChange:this.onChange.bind(this),value:n}),_react.default.createElement("pre",{className:"textarea-overlay",style:{position:"absolute",top:0,backgroundColor:"rgba(255, 255, 255, 0)",whiteSpace:"pre-wrap"},dangerouslySetInnerHTML:{__html:l}})),_react.default.createElement("div",{style:{overflow:"auto",border:"1px solid #cfcfc4",padding:"0",borderBottomLeftRadius:"5px",borderBottomRightRadius:"5px"}},_react.default.createElement("div",{className:"time"},"Run took ",o,"ms"),_react.default.createElement("div",{className:"console"},_react.default.createElement("span",{className:"output"},a||i?"":_react.default.createElement("div",null,_react.default.createElement("pre",{style:{whiteSpace:"pre-wrap",margin:0,borderRadius:0}},"Output from the example appears here")),a?_react.default.createElement("div",null,_react.default.createElement("pre",{style:{whiteSpace:"pre-wrap",margin:0,borderRadius:0}},a.toString())):"",i&&i.length>0?i.map((e,t)=>{const{type:n,value:r}=e;return"html"===n?_react.default.createElement("pre",{key:`${t}/${Date.now()}`,style:{margin:"10px",border:"1px solid #f5f5f5",padding:"5px",position:"relative"}}," ",_react.default.createElement("div",{dangerouslySetInnerHTML:{__html:r.toString()}})," "):_react.default.createElement("pre",{key:`${t}/${Date.now()}`,style:{margin:"10px",border:"1px solid #f5f5f5",padding:"5px",position:"relative"}},r)}):""),_react.default.createElement("button",{className:"run",type:"button",onClick:this.run.bind(this)},"Run"))))}}Editor.propTypes={title:_propTypes.default.string,subtitle:_propTypes.default.string,value:_propTypes.default.string},module.exports=Editor},function(e,t,n){var r=n(28);"string"==typeof r&&(r=[[e.i,r,""]]);var o={hmr:!0,transform:void 0,insertInto:void 0};n(3)(r,o);r.locals&&(e.exports=r.locals)},function(e,t,n){(e.exports=n(2)(!1)).push([e.i,".panel, .panel-body, .ace_editor, .textarea, .textarea-container {\n border-top-left-radius: 5px;\n border-top-right-radius: 5px;\n}\n\n.panel, .panel-body, .panel-footer, .panel-header {\n padding: 0 !important;\n}\n\n.ace_editor {\n font-size: 1em !important;\n}\n\n.description {\n text-align: center;\n font-weight: 100;\n font-size: 18px;\n}\n\n.console {\n position: relative;\n border-top: 1px solid #CCC;\n font-family: monospace;\n line-height: 28px;\n min-height: 37px;\n}\n\n.console .output {\n color: #666;\n width: 100%;\n display: inline-block;\n}\n\n.console .output > pre {\n padding: 0;\n white-space: pre-wrap;\n background-color: transparent;\n}\n\n.time {\n color: #666;\n font-family: monospace;\n line-height: 13px;\n padding: 8px;\n font-size: 13px;\n}\n\n.run {\n padding: 12px 18px;\n margin: 10px;\n cursor: pointer;\n display: inline-block;\n text-align: center;\n border: 1px solid #cfcfc4;\n margin: 4px;\n position: absolute;\n top: 0;\n right: 0;\n background-color: rgb(81, 163, 81);\n color: white;\n border-radius: 0px;\n padding: 0 10px;\n line-height: 28px;\n}\n\n.textarea-container {\n position: relative;\n border: 1px solid #cfcfc4;\n border-bottom: 0;\n overflow: hidden;\n z-index: 100;\n}\n\n.textarea {\n resize: none;\n width: 100%;\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n color: transparent;\n background: transparent;\n color: black;\n text-shadow: 0px 0px 0px transparent;\n -webkit-text-fill-color: transparent;\n margin: 0;\n padding: 10px;\n outline: none;\n border: none;\n min-height: 40px;\n font: 13px/normal 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace;\n box-shadow: none;\n display: block;\n overflow: hidden;\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.textarea-overlay {\n resize: none;\n width: 100%;\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n margin: 0;\n padding: 10px;\n outline: none;\n border: none;\n min-height: 40px;\n font: 13px/normal 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace;\n box-shadow: none;\n display: block;\n overflow: hidden;\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.textarea--hidden {\n display: block;\n white-space: pre-wrap;\n word-wrap: break-word;\n visibility: hidden;\n position: absolute;\n top: 0;\n}\n",""])},function(e,t,n){var r=n(30);"string"==typeof r&&(r=[[e.i,r,""]]);var o={hmr:!0,transform:void 0,insertInto:void 0};n(3)(r,o);r.locals&&(e.exports=r.locals)},function(e,t,n){(e.exports=n(2)(!1)).push([e.i,".string {\n color: #032f62\n}\n.keyword {\n color: #d73a49\n}\n.operator {\n color: #d73a49\n}\n.function {\n color: #005cc5\n}\n.class {\n color: #6f42c1\n}\n.comment {\n color: #6a737d\n}\n",""])},function(e,t,n){const{decode:r,parse:o}=n(32);e.exports=function(e){return o(e).replace(/\{#([a-z]+)#(.*?)#\}/g,(e,t,n)=>{let o=r(n);return"\0"!==o?`${o}`:""})}},function(e,t){const n={string:/("[^"]*"|'[^']*'|`[^`]*`)/g,comment:/(\/\*.*?\*\/|\/\/.*)/g,class:/\b([A-Z][a-z]+)\b/g,number:/\b([0-9]+(?:\.[0-9]+)?)\b/g,keyword:new RegExp("\\b("+["const","console","process","let","var","function","if","else","for","while","break","switch","case","do","new","continue","delete","return","this","true","false","throw","catch","typeof"].join("|")+")\\b","g"),function:/([\w+]*)\(.*\);?/g,operator:/([+|=|-|||!|<|>|%|*|~])/g};function r(e){return e.split("").map(e=>e.charCodeAt(0)>127?e:String.fromCharCode(e.charCodeAt(0)+10240)).join(" ")}e.exports={decode:function(e){return e.trim().split(" ").map(e=>e.charCodeAt(0)-10240>127?e:String.fromCharCode(e.charCodeAt(0)-10240)).join("")},encode:r,parse:function e(t){return Object.keys(n).forEach(o=>{t=t.replace(n[o],(t,n)=>{let i=t.replace(n,""),a=`{#${o}#${r(n)}#}`;if("function"==o&&i){let t=i.indexOf("("),n=i.lastIndexOf(")"),r=i.lastIndexOf(";"),o=i.replace(/./g,(e,o)=>{switch(o){case t:case n:case r:return"";default:return e}});i=`(${e(o)})${r>-1?";":""}`}return a+i})}),t}}},function(e,t,n){"use strict";var r=i(n(0)),o=i(n(1));function i(e){return e&&e.__esModule?e:{default:e}}const a=20;class l extends r.default.Component{constructor(e){super(e),this.state={height:a,value:e.value||""},this.setValue=this.setValue.bind(this),this.setFilledTextareaHeight=this.setFilledTextareaHeight.bind(this)}componentDidMount(){this.mounted=!0,this.setFilledTextareaHeight()}setFilledTextareaHeight(){this.mounted&&setTimeout(()=>{const e=this.hidden;this.setState({height:e.clientHeight})},5)}setValue(e){const{onChange:t}=this.props,{value:n}=e.target;this.setState({value:n}),t&&t(n)}getExpandableField(){const{height:e,value:t}=this.state,n=e<=a;return r.default.createElement("textarea",{className:"textarea",name:"textarea",id:"textarea",autoFocus:!0,defaultValue:t,style:{height:e,resize:n?"none":null},onChange:this.setValue,onKeyUp:this.setFilledTextareaHeight})}getGhostField(){return r.default.createElement("div",{className:"textarea textarea--hidden",ref:e=>{this.hidden=e},"aria-hidden":"true"},this.state.value)}render(){return r.default.createElement("div",{className:"textarea-container"},this.getExpandableField(),this.getGhostField())}}l.propTypes={value:o.default.string,onChange:o.default.function},e.exports=l}]); \ No newline at end of file diff --git a/example/index.js b/example/index.js index 1e856d8..9a6c3bb 100644 --- a/example/index.js +++ b/example/index.js @@ -24,8 +24,8 @@ const port = process.env.PORT || 7005; const repos = new Server(path.normalize(path.resolve(__dirname, 'tmp')), { autoCreate: true, - authenticate: (type, repo, user, next) => { - console.log(type, repo); // eslint-disable-line + authenticate: ({ type, repo, user, headers }, next) => { + console.log(type, repo, headers); // eslint-disable-line if(type == 'push') { user((username, password) => { console.log(username, password); // eslint-disable-line diff --git a/lib/git.js b/lib/git.js index 9f9f129..7a49585 100644 --- a/lib/git.js +++ b/lib/git.js @@ -118,14 +118,14 @@ class Git extends EventEmitter { * @param {Object} options - options that can be applied on the new instance being created * @param {Boolean=} options.autoCreate - By default, repository targets will be created if they don't exist. You can disable that behavior with `options.autoCreate = true` - * @param {Function} options.authenticate - a function that has the following arguments (repo, username, password, next) and will be called when a request comes through if set + * @param {Function} options.authenticate - a function that has the following arguments ({ type, repo, username, password, headers }, next) and will be called when a request comes through if set * - authenticate: (type, repo, username, password, next) => { + authenticate: ({ type, repo, username, password, headers }, next) => { console.log(type, repo, username, password); next(); } // alternatively you can also pass authenticate a promise - authenticate: (type, repo, username, password, next) => { + authenticate: ({ type, repo, username, password, headers }, next) => { console.log(type, repo, username, password); return new Promise((resolve, reject) => { if(username === 'foo') { @@ -302,9 +302,12 @@ class Git extends EventEmitter { // check if the repo is authenticated if(this.authenticate) { const type = this.getType(service); - const promise = this.authenticate(type, repoName, basicAuth.bind(null, req, res), (error) => { + const headers = req.headers; + const user = basicAuth.bind(null, req, res); + const promise = this.authenticate({ type, repo: repoName, user, headers }, (error) => { return next(error); }); + if(promise instanceof Promise) { return promise .then(next) @@ -456,7 +459,7 @@ class Git extends EventEmitter { * @memberof Git * @param {Promise} - will resolve or reject when the server closes or fails to close. */ - close(callback) { + close() { return new Promise((resolve, reject) => { this.server.close((err) => { err ? reject(err) : resolve(); diff --git a/package.json b/package.json index 6c5e249..c442583 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-git-server", - "version": "0.5.1", + "version": "0.6.0", "description": "🎡 A configurable git server written in Node.js", "author": "Gabriel J. Csapo ", "contributors": [ diff --git a/test/git.js b/test/git.js index a56330e..588d586 100644 --- a/test/git.js +++ b/test/git.js @@ -10,7 +10,7 @@ const async = require('async'); const GitServer = require('../'); test('git', (t) => { - t.plan(9); + t.plan(10); t.test('create, push to, and clone a repo', (t) => { var lastCommit; @@ -648,7 +648,7 @@ test('git', (t) => { const repos = new GitServer(repoDir, { autoCreate: true, - authenticate: (type, repo, user, next) => { + authenticate: ({ type, repo, user }, next) => { if (type == 'download', repo == 'doom') { user((username, password) => { @@ -700,6 +700,76 @@ test('git', (t) => { }); + t.test('should be able to access headers in authenticate', (t) => { + t.plan(14); + + const repoDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; + const srcDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; + const dstDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; + + fs.mkdirSync(repoDir, 0700); + fs.mkdirSync(srcDir, 0700); + fs.mkdirSync(dstDir, 0700); + + const repos = new GitServer(repoDir, { + autoCreate: true, + authenticate: ({ type, repo, user, headers }, next) => { + if (type == 'download', repo == 'doom') { + t.ok(headers['host']); + t.ok(headers['user-agent']); + t.ok(headers['accept']); + t.ok(headers['pragma']); + t.ok(headers['accept-encoding']); + + user((username, password) => { + if (username == 'root' && password == 'root') { + next(); + } else { + next('that is not the correct password'); + } + }); + } else { + next('that is not the correct password'); + } + } + }); + const port = Math.floor(Math.random() * ((1 << 16) - 1e4)) + 1e4; + repos.listen(port); + + process.chdir(srcDir); + async.waterfall([ + (callback) => { + process.chdir(dstDir); + const clone = spawn('git', ['clone', `http://root:root@localhost:${port}/doom.git`]); + + clone.on('close', function(code) { + t.equal(code, 0); + callback(); + }); + }, + (callback) => { + process.chdir(dstDir); + const clone = spawn('git', ['clone', `http://root:world@localhost:${port}/doom.git doom1`]); + let error = ''; + + clone.stderr.on('data', (d) => { + error += d.toString('utf8'); + }); + + clone.on('close', function(code) { + t.equal(error, `Cloning into 'doom.git doom1'...\nfatal: unable to access 'http://root:world@localhost:${port}/doom.git doom1/': Empty reply from server\n`); + t.equal(code, 128); + callback(); + }); + } + ], (err) => { + t.ok(!err, 'no errors'); + repos.close(); + t.end(); + }); + + }); + t.test('should be able to protect certain routes with a promised authenticate', (t) => { const repoDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; const srcDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; @@ -711,7 +781,7 @@ test('git', (t) => { const repos = new GitServer(repoDir, { autoCreate: true, - authenticate: (type, repo, user) => { + authenticate: ({ type, repo, user }) => { return new Promise(function(resolve, reject) { if (type == 'download', repo == 'doom') { user((username, password) => { From e3ae3737fc9de848856e56a3cf624fe014803f25 Mon Sep 17 00:00:00 2001 From: pitust <41321673+pitust@users.noreply.github.com> Date: Sun, 28 Apr 2019 05:51:05 +0100 Subject: [PATCH 20/26] Fix an example (#55) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c897e9d..ec033c1 100644 --- a/README.md +++ b/README.md @@ -129,7 +129,7 @@ const Server = require('node-git-server'); const repos = new Server(path.resolve(__dirname, 'tmp'), { autoCreate: true, - authenticate: (type, repo, user, next) => { + authenticate: ({type, repo, user}, next) => { if(type == 'push') { user((username, password) => { console.log(username, password); From ac26650f69bc445d71e4f2c55328676d10a4be43 Mon Sep 17 00:00:00 2001 From: Ron Masas Date: Sun, 29 Mar 2020 20:45:58 +0300 Subject: [PATCH 21/26] Security Issue (#62) It is currently possible to overwrite the `repoDir` by sending a repository name that starts with a "/", the `path.resolve` method prioritizes the second argument see the example below. path.resolve("/my/repo/folder","/etc"); // /etc This behavior gives an attacker the ability to create/write/pull repositories from an arbitrary absolute path, this issue could also impact authentication in some cases as it corrupts the repository name. --- lib/git.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/git.js b/lib/git.js index 7a49585..cc61fcc 100644 --- a/lib/git.js +++ b/lib/git.js @@ -143,7 +143,7 @@ class Git extends EventEmitter { this.dirMap = repoDir; } else { this.dirMap = (dir) => { - return (path.normalize(dir ? path.resolve(repoDir, dir) : repoDir)); + return (path.normalize(dir ? path.join(repoDir, dir) : repoDir)); }; } From 931a1c683da0ba84c15a5c032bac16652c676c0b Mon Sep 17 00:00:00 2001 From: Gabriel Csapo Date: Sun, 29 Mar 2020 10:47:41 -0700 Subject: [PATCH 22/26] 0.6.1 - Fixes bug with being able to overwrite git repos that a user doesn't have access to. @masasron --- CHANGELOG.md | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e19b933..1d54091 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 0.6.1 (03/03/2019) + +- Fixes bug with being able to overwrite git repos that a user doesn't have access to. @masasron + # 0.6.0 (03/03/2019) - Augments the authenticate function declaration to accept an object as the first argument and a callback for the second. This allows us to make changes without having to cause breaking changes. diff --git a/package.json b/package.json index c442583..958d09d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-git-server", - "version": "0.6.0", + "version": "0.6.1", "description": "🎡 A configurable git server written in Node.js", "author": "Gabriel J. Csapo ", "contributors": [ From 5279f64b200b3d8e8f3d537c28c86793033e6285 Mon Sep 17 00:00:00 2001 From: Gabriel Csapo Date: Sun, 29 Mar 2020 19:41:05 -0700 Subject: [PATCH 23/26] [testing] test against lts node versions --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7fa2ea8..a01f4a1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,8 +9,8 @@ script: - npm run coverage - cat coverage/lcov.info | lcov-server --upload https://lcov-server.gabrielcsapo.com node_js: - - "6" - - "8" + - "10" + - "12" os: - linux sudo: false From 3f7407dd9a4042b03f61a287a26ede22ddb61cff Mon Sep 17 00:00:00 2001 From: Callum Macdonald Date: Mon, 14 Sep 2020 18:39:57 +0200 Subject: [PATCH 24/26] tag.version is the tag not the repo (#72) --- lib/git.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/git.js b/lib/git.js index cc61fcc..1093c6e 100644 --- a/lib/git.js +++ b/lib/git.js @@ -36,7 +36,7 @@ const services = ['upload-pack', 'receive-pack']; * @property {HttpDuplex} tag - an http duplex object (see below) with these extra properties: * @property {String} tag.repo - the string that defines the repo * @property {String} tag.commit - the string that defines the commit sha - * @property {String} tag.version - the string that defines the repo + * @property {String} tag.version - the string that defines the tag being pushed * @example repos.on('tag', function (tag) { ... } From a7682b7179999b4ca6ecbce6f396ba31d206a2a6 Mon Sep 17 00:00:00 2001 From: Callum Macdonald Date: Mon, 14 Sep 2020 18:40:07 +0200 Subject: [PATCH 25/26] Swap upload / download to fetch / push. (#73) --- lib/git.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/git.js b/lib/git.js index 1093c6e..ed5e4ba 100644 --- a/lib/git.js +++ b/lib/git.js @@ -241,7 +241,7 @@ class Git extends EventEmitter { * returns the typeof service being process * @method getType * @param {String} service - the service type - * @return {String} - will respond with either upload or download + * @return {String} - will respond with either fetch or push */ getType(service) { switch(service) { From 54adccd4f01ad69712b8305a9821a92d32080314 Mon Sep 17 00:00:00 2001 From: Matt Wynne Date: Sun, 14 Mar 2021 22:32:30 -0700 Subject: [PATCH 26/26] Hack in a change to allow pushing refs that are not tags or branches --- lib/service.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/service.js b/lib/service.js index 081a8be..b05e52c 100644 --- a/lib/service.js +++ b/lib/service.js @@ -7,8 +7,9 @@ const { spawn } = require('child_process'); const HttpDuplex = require('./http-duplex'); const headerRE = { - 'receive-pack': '([0-9a-fA-F]+) ([0-9a-fA-F]+) refs\/(heads|tags)\/(.*?)( |00|\u0000)|^(0000)$', // eslint-disable-line - 'upload-pack': '^\\S+ ([0-9a-fA-F]+)' + "receive-pack": + "([0-9a-fA-F]+) ([0-9a-fA-F]+) refs/([^/]+)/(.*?)( |00|\u0000)|^(0000)$", // eslint-disable-line + "upload-pack": "^\\S+ ([0-9a-fA-F]+)", }; const packSideband = s => {