/test-revision-walking
/test-run-command
/test-sha1
+/test-sha1-array
/test-sigchain
/test-string-list
/test-subprocess
UI, Workflows & Features
+ * "git archive" learned to filter what gets archived with pathspec.
+
* "git config --edit --global" starts from a skeletal per-user
configuration file contents, instead of a total blank, when the
user does not already have any. This immediately reduces the
for a tagged commit, gained a cousin "%D" that just gives the
"tagname" without frills.
+ * "git push" learned "--signed" push, that allows a push (i.e.
+ request to update the refs on the other side to point at a new
+ history, together with the transmission of necessary objects) to be
+ signed, so that it can be verified and audited, using the GPG
+ signature of the person who pushed, that the tips of branches at a
+ public repository really point the commits the pusher wanted to,
+ without having to "trust" the server.
+
Performance, Internal Implementation, etc.
* The API to manipulate the "refs" is currently undergoing a revamp
* "git hash-object" was taught a "--literally" option to help
debugging.
+ * When running a required clean filter, we do not have to mmap the
+ original before feeding the filter. Instead, stream the file
+ contents directly to the filter and process its output.
Also contains various documentation updates and code clean-ups.
receiving data from git-push and updating refs. You can stop
it by setting this variable to false.
+receive.certnonceseed::
+ By setting this variable to a string, `git receive-pack`
+ will accept a `git push --signed` and verifies it by using
+ a "nonce" protected by HMAC using this string as a secret
+ key.
+
+receive.certnonceslop::
+ When a `git push --signed` sent a push certificate with a
+ "nonce" that was issued by a receive-pack serving the same
+ repository within this many seconds, export the "nonce"
+ found in the certificate to `GIT_PUSH_CERT_NONCE` to the
+ hooks (instead of what the receive-pack asked the sending
+ side to include). This may allow writing checks in
+ `pre-receive` and `post-receive` a bit easier. Instead of
+ checking `GIT_PUSH_CERT_NONCE_SLOP` environment variable
+ that records by how many seconds the nonce is stale to
+ decide if they want to accept the certificate, they only
+ can check `GIT_PUSH_CERT_NONCE_STATUS` is `OK`.
+
receive.fsckObjects::
If it is set to true, git-receive-pack will check all received
objects. It will abort in the case of a malformed object or a
--------
[verse]
'git push' [--all | --mirror | --tags] [--follow-tags] [-n | --dry-run] [--receive-pack=<git-receive-pack>]
- [--repo=<repository>] [-f | --force] [--prune] [-v | --verbose] [-u | --set-upstream]
+ [--repo=<repository>] [-f | --force] [--prune] [-v | --verbose]
+ [-u | --set-upstream] [--signed]
[--force-with-lease[=<refname>[:<expect>]]]
[--no-verify] [<repository> [<refspec>...]]
from the remote but are pointing at commit-ish that are
reachable from the refs being pushed.
+--signed::
+ GPG-sign the push request to update refs on the receiving
+ side, to allow it to be checked by the hooks and/or be
+ logged. See linkgit:git-receive-pack[1] for the details
+ on the receiving end.
+
--receive-pack=<git-receive-pack>::
--exec=<git-receive-pack>::
Path to the 'git-receive-pack' program on the remote
it remains on the current branch.
If <upstream> is not specified, the upstream configured in
-branch.<name>.remote and branch.<name>.merge options will be used; see
-linkgit:git-config[1] for details. If you are currently not on any
-branch or if the current branch does not have a configured upstream,
-the rebase will abort.
+branch.<name>.remote and branch.<name>.merge options will be used (see
+linkgit:git-config[1] for details) and the `--fork-point` option is
+assumed. If you are currently not on any branch or if the current
+branch does not have a configured upstream, the rebase will abort.
All changes made by commits in the current branch but that are not
in <upstream> are saved to a temporary area. This is the same set
-of commits that would be shown by `git log <upstream>..HEAD` (or
-`git log HEAD`, if --root is specified).
+of commits that would be shown by `git log <upstream>..HEAD`; or by
+`git log 'fork_point'..HEAD`, if `--fork-point` is active (see the
+description on `--fork-point` below); or by `git log HEAD`, if the
+`--root` option is specified.
The current branch is reset to <upstream>, or <newbase> if the
--onto option was supplied. This has the exact same effect as
--fork-point::
--no-fork-point::
- Use 'git merge-base --fork-point' to find a better common ancestor
- between `upstream` and `branch` when calculating which commits have
- have been introduced by `branch` (see linkgit:git-merge-base[1]).
+ Use reflog to find a better common ancestor between <upstream>
+ and <branch> when calculating which commits have been
+ introduced by <branch>.
+
-If no non-option arguments are given on the command line, then the default is
-`--fork-point @{u}` otherwise the `upstream` argument is interpreted literally
-unless the `--fork-point` option is specified.
+When --fork-point is active, 'fork_point' will be used instead of
+<upstream> to calculate the set of commits to rebase, where
+'fork_point' is the result of `git merge-base --fork-point <upstream>
+<branch>` command (see linkgit:git-merge-base[1]). If 'fork_point'
+ends up being empty, the <upstream> will be used as a fallback.
++
+If either <upstream> or --root is given on the command line, then the
+default is `--no-fork-point`, otherwise the default is `--fork-point`.
--ignore-whitespace::
--whitespace=<option>::
while refs to be deleted will have sha1-new equal to 0\{40}, otherwise
sha1-old and sha1-new should be valid objects in the repository.
+When accepting a signed push (see linkgit:git-push[1]), the signed
+push certificate is stored in a blob and an environment variable
+`GIT_PUSH_CERT` can be consulted for its object name. See the
+description of `post-receive` hook for an example. In addition, the
+certificate is verified using GPG and the result is exported with
+the following environment variables:
+
+`GIT_PUSH_CERT_SIGNER`::
+ The name and the e-mail address of the owner of the key that
+ signed the push certificate.
+
+`GIT_PUSH_CERT_KEY`::
+ The GPG key ID of the key that signed the push certificate.
+
+`GIT_PUSH_CERT_STATUS`::
+ The status of GPG verification of the push certificate,
+ using the same mnemonic as used in `%G?` format of `git log`
+ family of commands (see linkgit:git-log[1]).
+
+`GIT_PUSH_CERT_NONCE`::
+ The nonce string the process asked the signer to include
+ in the push certificate. If this does not match the value
+ recorded on the "nonce" header in the push certificate, it
+ may indicate that the certificate is a valid one that is
+ being replayed from a separate "git push" session.
+
+`GIT_PUSH_CERT_NONCE_STATUS`::
+`UNSOLICITED`;;
+ "git push --signed" sent a nonce when we did not ask it to
+ send one.
+`MISSING`;;
+ "git push --signed" did not send any nonce header.
+`BAD`;;
+ "git push --signed" sent a bogus nonce.
+`OK`;;
+ "git push --signed" sent the nonce we asked it to send.
+`SLOP`;;
+ "git push --signed" sent a nonce different from what we
+ asked it to send now, but in a previous session. See
+ `GIT_PUSH_CERT_NONCE_SLOP` environment variable.
+
+`GIT_PUSH_CERT_NONCE_SLOP`::
+ "git push --signed" sent a nonce different from what we
+ asked it to send now, but in a different session whose
+ starting time is different by this many seconds from the
+ current session. Only meaningful when
+ `GIT_PUSH_CERT_NONCE_STATUS` says `SLOP`.
+ Also read about `receive.certnonceslop` variable in
+ linkgit:git-config[1].
+
This hook is called before any refname is updated and before any
fast-forward checks are performed.
0\{40}, otherwise sha1-old and sha1-new should be valid objects in
the repository.
+The `GIT_PUSH_CERT*` environment variables can be inspected, just as
+in `pre-receive` hook, after accepting a signed push.
+
Using this hook, it is easy to generate mails describing the updates
to the repository. This example script sends one mail message per
-ref listing the commits pushed to the repository:
+ref listing the commits pushed to the repository, and logs the push
+certificates of signed pushes with good signatures to a logger
+service:
#!/bin/sh
# mail out commit update information.
fi |
mail -s "Changes to ref $ref" commit-list@mydomain
done
+ # log signed push certificate, if any
+ if test -n "${GIT_PUSH_CERT-}" && test ${GIT_PUSH_CERT_STATUS} = G
+ then
+ (
+ echo expected nonce is ${GIT_PUSH_NONCE}
+ git cat-file blob ${GIT_PUSH_CERT}
+ ) | mail -s "push certificate from $GIT_PUSH_CERT_SIGNER" push-log@mydomain
+ fi
exit 0
The exit code from this hook invocation is ignored, however a
GnuPG key for signing. The configuration variable `gpg.program`
is used to specify custom GnuPG binary.
-Tag objects (created with `-a`, `s`, or `-u`) are called "annotated"
+Tag objects (created with `-a`, `-s`, or `-u`) are called "annotated"
tags; they contain a creation date, the tagger name and e-mail, a
tagging message, and an optional GnuPG signature. Whereas a
"lightweight" tag is simply a name for an object (usually a commit
The lockfile API serves two purposes:
-* Mutual exclusion. When we write out a new index file, first
- we create a new file `$GIT_DIR/index.lock`, write the new
- contents into it, and rename it to the final destination
- `$GIT_DIR/index`. We try to create the `$GIT_DIR/index.lock`
- file with O_EXCL so that we can notice and fail when somebody
- else is already trying to update the index file.
-
-* Automatic cruft removal. After we create the "lock" file, we
- may decide to `die()`, and we would want to make sure that we
- remove the file that has not been committed to its final
- destination. This is done by remembering the lockfiles we
- created in a linked list and cleaning them up from an
- `atexit(3)` handler. Outstanding lockfiles are also removed
- when the program dies on a signal.
+* Mutual exclusion and atomic file updates. When we want to change a
+ file, we create a lockfile `<filename>.lock`, write the new file
+ contents into it, and then rename the lockfile to its final
+ destination `<filename>`. We create the `<filename>.lock` file with
+ `O_CREAT|O_EXCL` so that we can notice and fail if somebody else has
+ already locked the file, then atomically rename the lockfile to its
+ final destination to commit the changes and unlock the file.
+
+* Automatic cruft removal. If the program exits after we lock a file
+ but before the changes have been committed, we want to make sure
+ that we remove the lockfile. This is done by remembering the
+ lockfiles we have created in a linked list and setting up an
+ `atexit(3)` handler and a signal handler that clean up the
+ lockfiles. This mechanism ensures that outstanding lockfiles are
+ cleaned up if the program exits (including when `die()` is called)
+ or if the program dies on a signal.
+
+Please note that lockfiles only block other writers. Readers do not
+block, but they are guaranteed to see either the old contents of the
+file or the new contents of the file (assuming that the filesystem
+implements `rename(2)` atomically).
+
+
+Calling sequence
+----------------
+
+The caller:
+
+* Allocates a `struct lock_file` either as a static variable or on the
+ heap, initialized to zeros. Once you use the structure to call the
+ `hold_lock_file_*` family of functions, it belongs to the lockfile
+ subsystem and its storage must remain valid throughout the life of
+ the program (i.e. you cannot use an on-stack variable to hold this
+ structure).
+
+* Attempts to create a lockfile by passing that variable and the path
+ of the final destination (e.g. `$GIT_DIR/index`) to
+ `hold_lock_file_for_update` or `hold_lock_file_for_append`.
+
+* Writes new content for the destination file by either:
+
+ * writing to the file descriptor returned by the `hold_lock_file_*`
+ functions (also available via `lock->fd`).
+
+ * calling `fdopen_lock_file` to get a `FILE` pointer for the open
+ file and writing to the file using stdio.
+
+When finished writing, the caller can:
+
+* Close the file descriptor and rename the lockfile to its final
+ destination by calling `commit_lock_file` or `commit_lock_file_to`.
+
+* Close the file descriptor and remove the lockfile by calling
+ `rollback_lock_file`.
+
+* Close the file descriptor without removing or renaming the lockfile
+ by calling `close_lock_file`, and later call `commit_lock_file`,
+ `commit_lock_file_to`, `rollback_lock_file`, or `reopen_lock_file`.
+
+Even after the lockfile is committed or rolled back, the `lock_file`
+object must not be freed or altered by the caller. However, it may be
+reused; just pass it to another call of `hold_lock_file_for_update` or
+`hold_lock_file_for_append`.
+
+If the program exits before you have called one of `commit_lock_file`,
+`commit_lock_file_to`, `rollback_lock_file`, or `close_lock_file`, an
+`atexit(3)` handler will close and remove the lockfile, rolling back
+any uncommitted changes.
+
+If you need to close the file descriptor you obtained from a
+`hold_lock_file_*` function yourself, do so by calling
+`close_lock_file`. You should never call `close(2)` or `fclose(3)`
+yourself! Otherwise the `struct lock_file` structure would still think
+that the file descriptor needs to be closed, and a commit or rollback
+would result in duplicate calls to `close(2)`. Worse yet, if you close
+and then later open another file descriptor for a completely different
+purpose, then a commit or rollback might close that unrelated file
+descriptor.
+
+
+Error handling
+--------------
+
+The `hold_lock_file_*` functions return a file descriptor on success
+or -1 on failure (unless `LOCK_DIE_ON_ERROR` is used; see below). On
+errors, `errno` describes the reason for failure. Errors can be
+reported by passing `errno` to one of the following helper functions:
+
+unable_to_lock_message::
+
+ Append an appropriate error message to a `strbuf`.
+
+unable_to_lock_error::
+
+ Emit an appropriate error message using `error()`.
+
+unable_to_lock_die::
+
+ Emit an appropriate error message and `die()`.
+
+Similarly, `commit_lock_file`, `commit_lock_file_to`, and
+`close_lock_file` return 0 on success. On failure they set `errno`
+appropriately, do their best to roll back the lockfile, and return -1.
+
+
+Flags
+-----
+
+The following flags can be passed to `hold_lock_file_for_update` or
+`hold_lock_file_for_append`:
+
+LOCK_NO_DEREF::
+
+ Usually symbolic links in the destination path are resolved
+ and the lockfile is created by adding ".lock" to the resolved
+ path. If `LOCK_NO_DEREF` is set, then the lockfile is created
+ by adding ".lock" to the path argument itself. This option is
+ used, for example, when locking a symbolic reference, which
+ for backwards-compatibility reasons can be a symbolic link
+ containing the name of the referred-to-reference.
+
+LOCK_DIE_ON_ERROR::
+
+ If a lock is already taken for the file, `die()` with an error
+ message. If this option is not specified, trying to lock a
+ file that is already locked returns -1 to the caller.
The functions
hold_lock_file_for_update::
- Take a pointer to `struct lock_file`, the filename of
- the final destination (e.g. `$GIT_DIR/index`) and a flag
- `die_on_error`. Attempt to create a lockfile for the
- destination and return the file descriptor for writing
- to the file. If `die_on_error` flag is true, it dies if
- a lock is already taken for the file; otherwise it
- returns a negative integer to the caller on failure.
+ Take a pointer to `struct lock_file`, the path of the file to
+ be locked (e.g. `$GIT_DIR/index`) and a flags argument (see
+ above). Attempt to create a lockfile for the destination and
+ return the file descriptor for writing to the file.
+
+hold_lock_file_for_append::
+
+ Like `hold_lock_file_for_update`, but before returning copy
+ the existing contents of the file (if any) to the lockfile and
+ position its write pointer at the end of the file.
+
+fdopen_lock_file::
+
+ Associate a stdio stream with the lockfile. Return NULL
+ (*without* rolling back the lockfile) on error. The stream is
+ closed automatically when `close_lock_file` is called or when
+ the file is committed or rolled back.
+
+get_locked_file_path::
+
+ Return the path of the file that is locked by the specified
+ lock_file object. The caller must free the memory.
commit_lock_file::
- Take a pointer to the `struct lock_file` initialized
- with an earlier call to `hold_lock_file_for_update()`,
- close the file descriptor and rename the lockfile to its
- final destination. Returns 0 upon success, a negative
- value on failure to close(2) or rename(2).
+ Take a pointer to the `struct lock_file` initialized with an
+ earlier call to `hold_lock_file_for_update` or
+ `hold_lock_file_for_append`, close the file descriptor, and
+ rename the lockfile to its final destination. Return 0 upon
+ success. On failure, roll back the lock file and return -1,
+ with `errno` set to the value from the failing call to
+ `close(2)` or `rename(2)`. It is a bug to call
+ `commit_lock_file` for a `lock_file` object that is not
+ currently locked.
+
+commit_lock_file_to::
+
+ Like `commit_lock_file()`, except that it takes an explicit
+ `path` argument to which the lockfile should be renamed. The
+ `path` must be on the same filesystem as the lock file.
rollback_lock_file::
- Take a pointer to the `struct lock_file` initialized
- with an earlier call to `hold_lock_file_for_update()`,
- close the file descriptor and remove the lockfile.
+ Take a pointer to the `struct lock_file` initialized with an
+ earlier call to `hold_lock_file_for_update` or
+ `hold_lock_file_for_append`, close the file descriptor and
+ remove the lockfile. It is a NOOP to call
+ `rollback_lock_file()` for a `lock_file` object that has
+ already been committed or rolled back.
close_lock_file::
- Take a pointer to the `struct lock_file` initialized
- with an earlier call to `hold_lock_file_for_update()`,
- and close the file descriptor. Returns 0 upon success,
- a negative value on failure to close(2).
-
-Because the structure is used in an `atexit(3)` handler, its
-storage has to stay throughout the life of the program. It
-cannot be an auto variable allocated on the stack.
-
-Call `commit_lock_file()` or `rollback_lock_file()` when you are
-done writing to the file descriptor. If you do not call either
-and simply `exit(3)` from the program, an `atexit(3)` handler
-will close and remove the lockfile.
-
-If you need to close the file descriptor you obtained from
-`hold_lock_file_for_update` function yourself, do so by calling
-`close_lock_file()`. You should never call `close(2)` yourself!
-Otherwise the `struct
-lock_file` structure still remembers that the file descriptor
-needs to be closed, and a later call to `commit_lock_file()` or
-`rollback_lock_file()` will result in duplicate calls to
-`close(2)`. Worse yet, if you `close(2)`, open another file
-descriptor for completely different purpose, and then call
-`commit_lock_file()` or `rollback_lock_file()`, they may close
-that unrelated file descriptor.
+
+ Take a pointer to the `struct lock_file` initialized with an
+ earlier call to `hold_lock_file_for_update` or
+ `hold_lock_file_for_append`. Close the file descriptor (and
+ the file pointer if it has been opened using
+ `fdopen_lock_file`). Return 0 upon success. On failure to
+ `close(2)`, return a negative value and roll back the lock
+ file. Usually `commit_lock_file`, `commit_lock_file_to`, or
+ `rollback_lock_file` should eventually be called if
+ `close_lock_file` succeeds.
+
+reopen_lock_file::
+
+ Re-open a lockfile that has been closed (using
+ `close_lock_file`) but not yet committed or rolled back. This
+ can be used to implement a sequence of operations like the
+ following:
+
+ * Lock file.
+
+ * Write new contents to lockfile, then `close_lock_file` to
+ cause the contents to be written to disk.
+
+ * Pass the name of the lockfile to another program to allow it
+ (and nobody else) to inspect the contents you wrote, while
+ still holding the lock yourself.
+
+ * `reopen_lock_file` to reopen the lockfile. Make further
+ updates to the contents.
+
+ * `commit_lock_file` to make the final version permanent.
want-list = first-want
*additional-want
- shallow-line = PKT_LINE("shallow" SP obj-id)
+ shallow-line = PKT-LINE("shallow" SP obj-id)
- depth-request = PKT_LINE("deepen" SP depth)
+ depth-request = PKT-LINE("deepen" SP depth)
first-want = PKT-LINE("want" SP obj-id SP capability-list LF)
additional-want = PKT-LINE("want" SP obj-id LF)
references.
----
- update-request = *shallow command-list [pack-file]
+ update-request = *shallow ( command-list | push-cert ) [pack-file]
shallow = PKT-LINE("shallow" SP obj-id LF)
old-id = obj-id
new-id = obj-id
+ push-cert = PKT-LINE("push-cert" NUL capability-list LF)
+ PKT-LINE("certificate version 0.1" LF)
+ PKT-LINE("pusher" SP ident LF)
+ PKT-LINE("pushee" SP url LF)
+ PKT-LINE("nonce" SP nonce LF)
+ PKT-LINE(LF)
+ *PKT-LINE(command LF)
+ *PKT-LINE(gpg-signature-lines LF)
+ PKT-LINE("push-cert-end" LF)
+
pack-file = "PACK" 28*(OCTET)
----
If the receiving end does not support delete-refs, the sending end MUST
NOT ask for delete command.
+If the receiving end does not support push-cert, the sending end
+MUST NOT send a push-cert command. When a push-cert command is
+sent, command-list MUST NOT be sent; the commands recorded in the
+push certificate is used instead.
+
The pack-file MUST NOT be sent if the only command used is 'delete'.
A pack-file MUST be sent if either create or update command is used,
it will run any update hooks to make sure that the update is acceptable.
If all of that is fine, the server will then update the references.
+Push Certificate
+----------------
+
+A push certificate begins with a set of header lines. After the
+header and an empty line, the protocol commands follow, one per
+line.
+
+Currently, the following header fields are defined:
+
+`pusher` ident::
+ Identify the GPG key in "Human Readable Name <email@address>"
+ format.
+
+`pushee` url::
+ The repository URL (anonymized, if the URL contains
+ authentication material) the user who ran `git push`
+ intended to push into.
+
+`nonce` nonce::
+ The 'nonce' string the receiving repository asked the
+ pushing user to include in the certificate, to prevent
+ replay attacks.
+
+The GPG signature lines are a detached signature for the contents
+recorded in the push certificate before the signature block begins.
+The detached signature is used to certify that the commands were
+given by the pusher, who must be the signer.
+
Report Status
-------------
and server advertised. As a consequence of these rules, server MUST
NOT advertise capabilities it does not understand.
-The 'report-status', 'delete-refs', and 'quiet' capabilities are sent and
-recognized by the receive-pack (push to server) process.
+The 'report-status', 'delete-refs', 'quiet', and 'push-cert' capabilities
+are sent and recognized by the receive-pack (push to server) process.
The 'ofs-delta' and 'side-band-64k' capabilities are sent and recognized
by both upload-pack and receive-pack protocols. The 'agent' capability
If the upload-pack server advertises this capability, fetch-pack may
send "want" lines with SHA-1s that exist at the server but are not
advertised by upload-pack.
+
+push-cert=<nonce>
+-----------------
+
+The receive-pack server that advertises this capability is willing
+to accept a signed push certificate, and asks the <nonce> to be
+included in the push certificate. A send-pack client MUST NOT
+send a push-cert packet unless the receive-pack server advertises
+this capability.
TEST_PROGRAMS_NEED_X += test-run-command
TEST_PROGRAMS_NEED_X += test-scrap-cache-tree
TEST_PROGRAMS_NEED_X += test-sha1
+TEST_PROGRAMS_NEED_X += test-sha1-array
TEST_PROGRAMS_NEED_X += test-sigchain
TEST_PROGRAMS_NEED_X += test-string-list
TEST_PROGRAMS_NEED_X += test-subprocess
#include "archive.h"
#include "parse-options.h"
#include "unpack-trees.h"
+#include "dir.h"
static char const * const archive_usage[] = {
N_("git archive [options] <tree-ish> [<path>...]"),
check[1].attr = attr_export_subst;
}
+struct directory {
+ struct directory *up;
+ unsigned char sha1[20];
+ int baselen, len;
+ unsigned mode;
+ int stage;
+ char path[FLEX_ARRAY];
+};
+
struct archiver_context {
struct archiver_args *args;
write_archive_entry_fn_t write_entry;
+ struct directory *bottom;
};
static int write_archive_entry(const unsigned char *sha1, const char *base,
return write_entry(args, sha1, path.buf, path.len, mode);
}
+static void queue_directory(const unsigned char *sha1,
+ const char *base, int baselen, const char *filename,
+ unsigned mode, int stage, struct archiver_context *c)
+{
+ struct directory *d;
+ d = xmallocz(sizeof(*d) + baselen + 1 + strlen(filename));
+ d->up = c->bottom;
+ d->baselen = baselen;
+ d->mode = mode;
+ d->stage = stage;
+ c->bottom = d;
+ d->len = sprintf(d->path, "%.*s%s/", baselen, base, filename);
+ hashcpy(d->sha1, sha1);
+}
+
+static int write_directory(struct archiver_context *c)
+{
+ struct directory *d = c->bottom;
+ int ret;
+
+ if (!d)
+ return 0;
+ c->bottom = d->up;
+ d->path[d->len - 1] = '\0'; /* no trailing slash */
+ ret =
+ write_directory(c) ||
+ write_archive_entry(d->sha1, d->path, d->baselen,
+ d->path + d->baselen, d->mode,
+ d->stage, c) != READ_TREE_RECURSIVE;
+ free(d);
+ return ret ? -1 : 0;
+}
+
+static int queue_or_write_archive_entry(const unsigned char *sha1,
+ const char *base, int baselen, const char *filename,
+ unsigned mode, int stage, void *context)
+{
+ struct archiver_context *c = context;
+
+ while (c->bottom &&
+ !(baselen >= c->bottom->len &&
+ !strncmp(base, c->bottom->path, c->bottom->len))) {
+ struct directory *next = c->bottom->up;
+ free(c->bottom);
+ c->bottom = next;
+ }
+
+ if (S_ISDIR(mode)) {
+ queue_directory(sha1, base, baselen, filename,
+ mode, stage, c);
+ return READ_TREE_RECURSIVE;
+ }
+
+ if (write_directory(c))
+ return -1;
+ return write_archive_entry(sha1, base, baselen, filename, mode,
+ stage, context);
+}
+
int write_archive_entries(struct archiver_args *args,
write_archive_entry_fn_t write_entry)
{
return err;
}
+ memset(&context, 0, sizeof(context));
context.args = args;
context.write_entry = write_entry;
}
err = read_tree_recursive(args->tree, "", 0, 0, &args->pathspec,
- write_archive_entry, &context);
+ args->pathspec.has_wildcard ?
+ queue_or_write_archive_entry :
+ write_archive_entry,
+ &context);
if (err == READ_TREE_RECURSIVE)
err = 0;
+ while (context.bottom) {
+ struct directory *next = context.bottom->up;
+ free(context.bottom);
+ context.bottom = next;
+ }
return err;
}
int baselen, const char *filename, unsigned mode,
int stage, void *context)
{
- return -1;
+ int ret = -1;
+ if (S_ISDIR(mode)) {
+ struct strbuf sb = STRBUF_INIT;
+ strbuf_addstr(&sb, base);
+ strbuf_addstr(&sb, filename);
+ if (!match_pathspec(context, sb.buf, sb.len, 0, NULL, 1))
+ ret = READ_TREE_RECURSIVE;
+ strbuf_release(&sb);
+ }
+ return ret;
}
static int path_exists(struct tree *tree, const char *path)
int ret;
parse_pathspec(&pathspec, 0, 0, "", paths);
- ret = read_tree_recursive(tree, "", 0, 0, &pathspec, reject_entry, NULL);
+ pathspec.recursive = 1;
+ ret = read_tree_recursive(tree, "", 0, 0, &pathspec,
+ reject_entry, &pathspec);
free_pathspec(&pathspec);
return ret != 0;
}
parse_pathspec(&ar_args->pathspec, 0,
PATHSPEC_PREFER_FULL,
"", pathspec);
+ ar_args->pathspec.recursive = 1;
if (pathspec) {
while (*pathspec) {
if (**pathspec && !path_exists(ar_args->tree, *pathspec))
*/
#include "cache.h"
#include "builtin.h"
+#include "lockfile.h"
#include "dir.h"
#include "pathspec.h"
#include "exec_cmd.h"
*
*/
#include "cache.h"
+#include "lockfile.h"
#include "cache-tree.h"
#include "quote.h"
#include "blob.h"
*
*/
#include "builtin.h"
-#include "cache.h"
+#include "lockfile.h"
#include "quote.h"
#include "cache-tree.h"
#include "parse-options.h"
-#include "cache.h"
#include "builtin.h"
+#include "lockfile.h"
#include "parse-options.h"
#include "refs.h"
#include "commit.h"
*/
#include "builtin.h"
+#include "lockfile.h"
#include "parse-options.h"
#include "fetch-pack.h"
#include "refs.h"
*/
#include "cache.h"
+#include "lockfile.h"
#include "cache-tree.h"
#include "color.h"
#include "dir.h"
die_resolve_conflict("commit");
}
-static char *prepare_index(int argc, const char **argv, const char *prefix,
- const struct commit *current_head, int is_status)
+static const char *prepare_index(int argc, const char **argv, const char *prefix,
+ const struct commit *current_head, int is_status)
{
struct string_list partial;
struct pathspec pathspec;
die(_("unable to create temporary index"));
old_index_env = getenv(INDEX_ENVIRONMENT);
- setenv(INDEX_ENVIRONMENT, index_lock.filename, 1);
+ setenv(INDEX_ENVIRONMENT, index_lock.filename.buf, 1);
if (interactive_add(argc, argv, prefix, patch_interactive) != 0)
die(_("interactive add failed"));
unsetenv(INDEX_ENVIRONMENT);
discard_cache();
- read_cache_from(index_lock.filename);
+ read_cache_from(index_lock.filename.buf);
if (update_main_cache_tree(WRITE_TREE_SILENT) == 0) {
if (reopen_lock_file(&index_lock) < 0)
die(_("unable to write index file"));
warning(_("Failed to update main cache tree"));
commit_style = COMMIT_NORMAL;
- return index_lock.filename;
+ return index_lock.filename.buf;
}
/*
if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK))
die(_("unable to write new_index file"));
commit_style = COMMIT_NORMAL;
- return index_lock.filename;
+ return index_lock.filename.buf;
}
/*
die(_("unable to write temporary index file"));
discard_cache();
- read_cache_from(false_lock.filename);
+ read_cache_from(false_lock.filename.buf);
- return false_lock.filename;
+ return false_lock.filename.buf;
}
static int run_status(FILE *fp, const char *index_file, const char *prefix, int nowarn,
#include "cache.h"
+#include "lockfile.h"
#include "commit.h"
#include "tag.h"
#include "refs.h"
* Copyright (c) 2006 Junio C Hamano
*/
#include "cache.h"
+#include "lockfile.h"
#include "color.h"
#include "commit.h"
#include "blob.h"
*/
#include "builtin.h"
-#include "cache.h"
+#include "lockfile.h"
#include "parse-options.h"
#include "run-command.h"
#include "sigchain.h"
int is_bare = !is_from_line(buf.buf, buf.len);
if (is_bare && !allow_bare) {
- unlink(name);
fprintf(stderr, "corrupt mailbox\n");
exit(1);
}
#include "cache.h"
#include "parse-options.h"
#include "builtin.h"
+#include "lockfile.h"
#include "run-command.h"
#include "diff.h"
#include "refs.h"
struct commit_list *remoteheads,
struct commit *head, const char *head_arg)
{
- struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
+ static struct lock_file lock;
- hold_locked_index(lock, 1);
+ hold_locked_index(&lock, 1);
refresh_cache(REFRESH_QUIET);
if (active_cache_changed &&
- write_locked_index(&the_index, lock, COMMIT_LOCK))
+ write_locked_index(&the_index, &lock, COMMIT_LOCK))
return error(_("Unable to write index."));
- rollback_lock_file(lock);
+ rollback_lock_file(&lock);
if (!strcmp(strategy, "recursive") || !strcmp(strategy, "subtree")) {
int clean, x;
struct commit *result;
- struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
struct commit_list *reversed = NULL;
struct merge_options o;
struct commit_list *j;
for (j = common; j; j = j->next)
commit_list_insert(j->item, &reversed);
- hold_locked_index(lock, 1);
+ hold_locked_index(&lock, 1);
clean = merge_recursive(&o, head,
remoteheads->item, reversed, &result);
if (active_cache_changed &&
- write_locked_index(&the_index, lock, COMMIT_LOCK))
+ write_locked_index(&the_index, &lock, COMMIT_LOCK))
die (_("unable to write %s"), get_index_file());
- rollback_lock_file(lock);
+ rollback_lock_file(&lock);
return clean ? 0 : 1;
} else {
return try_merge_command(strategy, xopts_nr, xopts,
*
* Copyright (C) 2006 Johannes Schindelin
*/
-#include "cache.h"
#include "builtin.h"
+#include "lockfile.h"
#include "dir.h"
#include "cache-tree.h"
#include "string-list.h"
OPT_BIT(0, "no-verify", &flags, N_("bypass pre-push hook"), TRANSPORT_PUSH_NO_HOOK),
OPT_BIT(0, "follow-tags", &flags, N_("push missing but relevant tags"),
TRANSPORT_PUSH_FOLLOW_TAGS),
+ OPT_BIT(0, "signed", &flags, N_("GPG sign the push"), TRANSPORT_PUSH_CERT),
OPT_END()
};
*/
#include "cache.h"
+#include "lockfile.h"
#include "object.h"
#include "tree.h"
#include "tree-walk.h"
#include "builtin.h"
+#include "lockfile.h"
#include "pack.h"
#include "refs.h"
#include "pkt-line.h"
#include "connected.h"
#include "argv-array.h"
#include "version.h"
+#include "tag.h"
+#include "gpg-interface.h"
#include "sigchain.h"
static const char receive_pack_usage[] = "git receive-pack <git-dir>";
static int auto_update_server_info;
static int auto_gc = 1;
static int fix_thin = 1;
+static int stateless_rpc;
+static const char *service_dir;
static const char *head_name;
static void *head_name_to_free;
static int sent_capabilities;
static int shallow_update;
static const char *alt_shallow_file;
+static struct strbuf push_cert = STRBUF_INIT;
+static unsigned char push_cert_sha1[20];
+static struct signature_check sigcheck;
+static const char *push_cert_nonce;
+static const char *cert_nonce_seed;
+
+static const char *NONCE_UNSOLICITED = "UNSOLICITED";
+static const char *NONCE_BAD = "BAD";
+static const char *NONCE_MISSING = "MISSING";
+static const char *NONCE_OK = "OK";
+static const char *NONCE_SLOP = "SLOP";
+static const char *nonce_status;
+static long nonce_stamp_slop;
+static unsigned long nonce_stamp_slop_limit;
static enum deny_action parse_deny_action(const char *var, const char *value)
{
return 0;
}
+ if (strcmp(var, "receive.certnonceseed") == 0)
+ return git_config_string(&cert_nonce_seed, var, value);
+
+ if (strcmp(var, "receive.certnonceslop") == 0) {
+ nonce_stamp_slop_limit = git_config_ulong(var, value);
+ return 0;
+ }
+
return git_default_config(var, value, cb);
}
if (ref_is_hidden(path))
return;
- if (sent_capabilities)
+ if (sent_capabilities) {
packet_write(1, "%s %s\n", sha1_to_hex(sha1), path);
- else
- packet_write(1, "%s %s%c%s%s agent=%s\n",
- sha1_to_hex(sha1), path, 0,
- " report-status delete-refs side-band-64k quiet",
- prefer_ofs_delta ? " ofs-delta" : "",
- git_user_agent_sanitized());
- sent_capabilities = 1;
+ } else {
+ struct strbuf cap = STRBUF_INIT;
+
+ strbuf_addstr(&cap,
+ "report-status delete-refs side-band-64k quiet");
+ if (prefer_ofs_delta)
+ strbuf_addstr(&cap, " ofs-delta");
+ if (push_cert_nonce)
+ strbuf_addf(&cap, " push-cert=%s", push_cert_nonce);
+ strbuf_addf(&cap, " agent=%s", git_user_agent_sanitized());
+ packet_write(1, "%s %s%c%s\n",
+ sha1_to_hex(sha1), path, 0, cap.buf);
+ strbuf_release(&cap);
+ sent_capabilities = 1;
+ }
}
static int show_ref_cb(const char *path, const unsigned char *sha1, int flag, void *unused)
return 0;
}
+#define HMAC_BLOCK_SIZE 64
+
+static void hmac_sha1(unsigned char *out,
+ const char *key_in, size_t key_len,
+ const char *text, size_t text_len)
+{
+ unsigned char key[HMAC_BLOCK_SIZE];
+ unsigned char k_ipad[HMAC_BLOCK_SIZE];
+ unsigned char k_opad[HMAC_BLOCK_SIZE];
+ int i;
+ git_SHA_CTX ctx;
+
+ /* RFC 2104 2. (1) */
+ memset(key, '\0', HMAC_BLOCK_SIZE);
+ if (HMAC_BLOCK_SIZE < key_len) {
+ git_SHA1_Init(&ctx);
+ git_SHA1_Update(&ctx, key_in, key_len);
+ git_SHA1_Final(key, &ctx);
+ } else {
+ memcpy(key, key_in, key_len);
+ }
+
+ /* RFC 2104 2. (2) & (5) */
+ for (i = 0; i < sizeof(key); i++) {
+ k_ipad[i] = key[i] ^ 0x36;
+ k_opad[i] = key[i] ^ 0x5c;
+ }
+
+ /* RFC 2104 2. (3) & (4) */
+ git_SHA1_Init(&ctx);
+ git_SHA1_Update(&ctx, k_ipad, sizeof(k_ipad));
+ git_SHA1_Update(&ctx, text, text_len);
+ git_SHA1_Final(out, &ctx);
+
+ /* RFC 2104 2. (6) & (7) */
+ git_SHA1_Init(&ctx);
+ git_SHA1_Update(&ctx, k_opad, sizeof(k_opad));
+ git_SHA1_Update(&ctx, out, 20);
+ git_SHA1_Final(out, &ctx);
+}
+
+static char *prepare_push_cert_nonce(const char *path, unsigned long stamp)
+{
+ struct strbuf buf = STRBUF_INIT;
+ unsigned char sha1[20];
+
+ strbuf_addf(&buf, "%s:%lu", path, stamp);
+ hmac_sha1(sha1, buf.buf, buf.len, cert_nonce_seed, strlen(cert_nonce_seed));;
+ strbuf_release(&buf);
+
+ /* RFC 2104 5. HMAC-SHA1-80 */
+ strbuf_addf(&buf, "%lu-%.*s", stamp, 20, sha1_to_hex(sha1));
+ return strbuf_detach(&buf, NULL);
+}
+
+/*
+ * NEEDSWORK: reuse find_commit_header() from jk/commit-author-parsing
+ * after dropping "_commit" from its name and possibly moving it out
+ * of commit.c
+ */
+static char *find_header(const char *msg, size_t len, const char *key)
+{
+ int key_len = strlen(key);
+ const char *line = msg;
+
+ while (line && line < msg + len) {
+ const char *eol = strchrnul(line, '\n');
+
+ if ((msg + len <= eol) || line == eol)
+ return NULL;
+ if (line + key_len < eol &&
+ !memcmp(line, key, key_len) && line[key_len] == ' ') {
+ int offset = key_len + 1;
+ return xmemdupz(line + offset, (eol - line) - offset);
+ }
+ line = *eol ? eol + 1 : NULL;
+ }
+ return NULL;
+}
+
+static const char *check_nonce(const char *buf, size_t len)
+{
+ char *nonce = find_header(buf, len, "nonce");
+ unsigned long stamp, ostamp;
+ char *bohmac, *expect = NULL;
+ const char *retval = NONCE_BAD;
+
+ if (!nonce) {
+ retval = NONCE_MISSING;
+ goto leave;
+ } else if (!push_cert_nonce) {
+ retval = NONCE_UNSOLICITED;
+ goto leave;
+ } else if (!strcmp(push_cert_nonce, nonce)) {
+ retval = NONCE_OK;
+ goto leave;
+ }
+
+ if (!stateless_rpc) {
+ /* returned nonce MUST match what we gave out earlier */
+ retval = NONCE_BAD;
+ goto leave;
+ }
+
+ /*
+ * In stateless mode, we may be receiving a nonce issued by
+ * another instance of the server that serving the same
+ * repository, and the timestamps may not match, but the
+ * nonce-seed and dir should match, so we can recompute and
+ * report the time slop.
+ *
+ * In addition, when a nonce issued by another instance has
+ * timestamp within receive.certnonceslop seconds, we pretend
+ * as if we issued that nonce when reporting to the hook.
+ */
+
+ /* nonce is concat(<seconds-since-epoch>, "-", <hmac>) */
+ if (*nonce <= '0' || '9' < *nonce) {
+ retval = NONCE_BAD;
+ goto leave;
+ }
+ stamp = strtoul(nonce, &bohmac, 10);
+ if (bohmac == nonce || bohmac[0] != '-') {
+ retval = NONCE_BAD;
+ goto leave;
+ }
+
+ expect = prepare_push_cert_nonce(service_dir, stamp);
+ if (strcmp(expect, nonce)) {
+ /* Not what we would have signed earlier */
+ retval = NONCE_BAD;
+ goto leave;
+ }
+
+ /*
+ * By how many seconds is this nonce stale? Negative value
+ * would mean it was issued by another server with its clock
+ * skewed in the future.
+ */
+ ostamp = strtoul(push_cert_nonce, NULL, 10);
+ nonce_stamp_slop = (long)ostamp - (long)stamp;
+
+ if (nonce_stamp_slop_limit &&
+ abs(nonce_stamp_slop) <= nonce_stamp_slop_limit) {
+ /*
+ * Pretend as if the received nonce (which passes the
+ * HMAC check, so it is not a forged by third-party)
+ * is what we issued.
+ */
+ free((void *)push_cert_nonce);
+ push_cert_nonce = xstrdup(nonce);
+ retval = NONCE_OK;
+ } else {
+ retval = NONCE_SLOP;
+ }
+
+leave:
+ free(nonce);
+ free(expect);
+ return retval;
+}
+
+static void prepare_push_cert_sha1(struct child_process *proc)
+{
+ static int already_done;
+ struct argv_array env = ARGV_ARRAY_INIT;
+
+ if (!push_cert.len)
+ return;
+
+ if (!already_done) {
+ struct strbuf gpg_output = STRBUF_INIT;
+ struct strbuf gpg_status = STRBUF_INIT;
+ int bogs /* beginning_of_gpg_sig */;
+
+ already_done = 1;
+ if (write_sha1_file(push_cert.buf, push_cert.len, "blob", push_cert_sha1))
+ hashclr(push_cert_sha1);
+
+ memset(&sigcheck, '\0', sizeof(sigcheck));
+ sigcheck.result = 'N';
+
+ bogs = parse_signature(push_cert.buf, push_cert.len);
+ if (verify_signed_buffer(push_cert.buf, bogs,
+ push_cert.buf + bogs, push_cert.len - bogs,
+ &gpg_output, &gpg_status) < 0) {
+ ; /* error running gpg */
+ } else {
+ sigcheck.payload = push_cert.buf;
+ sigcheck.gpg_output = gpg_output.buf;
+ sigcheck.gpg_status = gpg_status.buf;
+ parse_gpg_output(&sigcheck);
+ }
+
+ strbuf_release(&gpg_output);
+ strbuf_release(&gpg_status);
+ nonce_status = check_nonce(push_cert.buf, bogs);
+ }
+ if (!is_null_sha1(push_cert_sha1)) {
+ argv_array_pushf(&env, "GIT_PUSH_CERT=%s", sha1_to_hex(push_cert_sha1));
+ argv_array_pushf(&env, "GIT_PUSH_CERT_SIGNER=%s",
+ sigcheck.signer ? sigcheck.signer : "");
+ argv_array_pushf(&env, "GIT_PUSH_CERT_KEY=%s",
+ sigcheck.key ? sigcheck.key : "");
+ argv_array_pushf(&env, "GIT_PUSH_CERT_STATUS=%c", sigcheck.result);
+ if (push_cert_nonce) {
+ argv_array_pushf(&env, "GIT_PUSH_CERT_NONCE=%s", push_cert_nonce);
+ argv_array_pushf(&env, "GIT_PUSH_CERT_NONCE_STATUS=%s", nonce_status);
+ if (nonce_status == NONCE_SLOP)
+ argv_array_pushf(&env, "GIT_PUSH_CERT_NONCE_SLOP=%ld",
+ nonce_stamp_slop);
+ }
+ proc->env = env.argv;
+ }
+}
+
typedef int (*feed_fn)(void *, const char **, size_t *);
static int run_and_feed_hook(const char *hook_name, feed_fn feed, void *feed_state)
{
proc.in = -1;
proc.stdout_to_stderr = 1;
+ prepare_push_cert_sha1(&proc);
+
if (use_sideband) {
memset(&muxer, 0, sizeof(muxer));
muxer.proc = copy_to_sideband;
"the reported refs above");
}
+static struct command **queue_command(struct command **tail,
+ const char *line,
+ int linelen)
+{
+ unsigned char old_sha1[20], new_sha1[20];
+ struct command *cmd;
+ const char *refname;
+ int reflen;
+
+ if (linelen < 83 ||
+ line[40] != ' ' ||
+ line[81] != ' ' ||
+ get_sha1_hex(line, old_sha1) ||
+ get_sha1_hex(line + 41, new_sha1))
+ die("protocol error: expected old/new/ref, got '%s'", line);
+
+ refname = line + 82;
+ reflen = linelen - 82;
+ cmd = xcalloc(1, sizeof(struct command) + reflen + 1);
+ hashcpy(cmd->old_sha1, old_sha1);
+ hashcpy(cmd->new_sha1, new_sha1);
+ memcpy(cmd->ref_name, refname, reflen);
+ cmd->ref_name[reflen] = '\0';
+ *tail = cmd;
+ return &cmd->next;
+}
+
+static void queue_commands_from_cert(struct command **tail,
+ struct strbuf *push_cert)
+{
+ const char *boc, *eoc;
+
+ if (*tail)
+ die("protocol error: got both push certificate and unsigned commands");
+
+ boc = strstr(push_cert->buf, "\n\n");
+ if (!boc)
+ die("malformed push certificate %.*s", 100, push_cert->buf);
+ else
+ boc += 2;
+ eoc = push_cert->buf + parse_signature(push_cert->buf, push_cert->len);
+
+ while (boc < eoc) {
+ const char *eol = memchr(boc, '\n', eoc - boc);
+ tail = queue_command(tail, boc, eol ? eol - boc : eoc - eol);
+ boc = eol ? eol + 1 : eoc;
+ }
+}
+
static struct command *read_head_info(struct sha1_array *shallow)
{
struct command *commands = NULL;
struct command **p = &commands;
for (;;) {
char *line;
- unsigned char old_sha1[20], new_sha1[20];
- struct command *cmd;
- char *refname;
- int len, reflen;
+ int len, linelen;
line = packet_read_line(0, &len);
if (!line)
break;
if (len == 48 && starts_with(line, "shallow ")) {
- if (get_sha1_hex(line + 8, old_sha1))
- die("protocol error: expected shallow sha, got '%s'", line + 8);
- sha1_array_append(shallow, old_sha1);
+ unsigned char sha1[20];
+ if (get_sha1_hex(line + 8, sha1))
+ die("protocol error: expected shallow sha, got '%s'",
+ line + 8);
+ sha1_array_append(shallow, sha1);
continue;
}
- if (len < 83 ||
- line[40] != ' ' ||
- line[81] != ' ' ||
- get_sha1_hex(line, old_sha1) ||
- get_sha1_hex(line + 41, new_sha1))
- die("protocol error: expected old/new/ref, got '%s'",
- line);
-
- refname = line + 82;
- reflen = strlen(refname);
- if (reflen + 82 < len) {
- const char *feature_list = refname + reflen + 1;
+ linelen = strlen(line);
+ if (linelen < len) {
+ const char *feature_list = line + linelen + 1;
if (parse_feature_request(feature_list, "report-status"))
report_status = 1;
if (parse_feature_request(feature_list, "side-band-64k"))
if (parse_feature_request(feature_list, "quiet"))
quiet = 1;
}
- cmd = xcalloc(1, sizeof(struct command) + len - 80);
- hashcpy(cmd->old_sha1, old_sha1);
- hashcpy(cmd->new_sha1, new_sha1);
- memcpy(cmd->ref_name, line + 82, len - 81);
- *p = cmd;
- p = &cmd->next;
+
+ if (!strcmp(line, "push-cert")) {
+ int true_flush = 0;
+ char certbuf[1024];
+
+ for (;;) {
+ len = packet_read(0, NULL, NULL,
+ certbuf, sizeof(certbuf), 0);
+ if (!len) {
+ true_flush = 1;
+ break;
+ }
+ if (!strcmp(certbuf, "push-cert-end\n"))
+ break; /* end of cert */
+ strbuf_addstr(&push_cert, certbuf);
+ }
+
+ if (true_flush)
+ break;
+ continue;
+ }
+
+ p = queue_command(p, line, linelen);
}
+
+ if (push_cert.len)
+ queue_commands_from_cert(p, &push_cert);
+
return commands;
}
int cmd_receive_pack(int argc, const char **argv, const char *prefix)
{
int advertise_refs = 0;
- int stateless_rpc = 0;
int i;
- const char *dir = NULL;
struct command *commands;
struct sha1_array shallow = SHA1_ARRAY_INIT;
struct sha1_array ref = SHA1_ARRAY_INIT;
usage(receive_pack_usage);
}
- if (dir)
+ if (service_dir)
usage(receive_pack_usage);
- dir = arg;
+ service_dir = arg;
}
- if (!dir)
+ if (!service_dir)
usage(receive_pack_usage);
setup_path();
- if (!enter_repo(dir, 0))
- die("'%s' does not appear to be a git repository", dir);
+ if (!enter_repo(service_dir, 0))
+ die("'%s' does not appear to be a git repository", service_dir);
git_config(receive_pack_config, NULL);
+ if (cert_nonce_seed)
+ push_cert_nonce = prepare_push_cert_nonce(service_dir, time(NULL));
if (0 <= transfer_unpack_limit)
unpack_limit = transfer_unpack_limit;
packet_flush(1);
sha1_array_clear(&shallow);
sha1_array_clear(&ref);
+ free((void *)push_cert_nonce);
return 0;
}
-#include "cache.h"
#include "builtin.h"
+#include "lockfile.h"
#include "commit.h"
#include "refs.h"
#include "dir.h"
write_str_in_full(lock->lock_fd, "\n") != 1 ||
close_ref(lock) < 0)) {
status |= error("Couldn't write %s",
- lock->lk->filename);
+ lock->lk->filename.buf);
unlink(newlog_path);
} else if (rename(newlog_path, log_file)) {
status |= error("cannot rename %s to %s",
* Copyright (c) 2005, 2006 Linus Torvalds and Junio C Hamano
*/
#include "builtin.h"
+#include "lockfile.h"
#include "tag.h"
#include "object.h"
#include "commit.h"
*
* Copyright (C) Linus Torvalds 2006
*/
-#include "cache.h"
#include "builtin.h"
+#include "lockfile.h"
#include "dir.h"
#include "cache-tree.h"
#include "tree-walk.h"
args.verbose = 1;
continue;
}
+ if (!strcmp(arg, "--signed")) {
+ args.push_cert = 1;
+ continue;
+ }
if (!strcmp(arg, "--progress")) {
progress = 1;
continue;
* Copyright (C) Linus Torvalds, 2005
*/
#include "cache.h"
+#include "lockfile.h"
#include "quote.h"
#include "cache-tree.h"
#include "tree-walk.h"
if (newfd < 0) {
if (refresh_args.flags & REFRESH_QUIET)
exit(128);
- unable_to_lock_index_die(get_index_file(), lock_error);
+ unable_to_lock_die(get_index_file(), lock_error);
}
if (write_locked_index(&the_index, lock_file, COMMIT_LOCK))
die("Unable to write new index file");
/*
* Copyright (c) 2011, Google Inc.
*/
+#include "cache.h"
#include "bulk-checkin.h"
#include "csum-file.h"
#include "pack.h"
#ifndef BULK_CHECKIN_H
#define BULK_CHECKIN_H
-#include "cache.h"
-
extern int index_bulk_checkin(unsigned char sha1[],
int fd, size_t size, enum object_type type,
const char *path, unsigned flags);
#include "cache.h"
+#include "lockfile.h"
#include "bundle.h"
#include "object.h"
#include "commit.h"
#include "cache.h"
+#include "lockfile.h"
#include "tree.h"
#include "tree-walk.h"
#include "cache-tree.h"
#define REFRESH_IN_PORCELAIN 0x0020 /* user friendly output, not "needs update" */
extern int refresh_index(struct index_state *, unsigned int flags, const struct pathspec *pathspec, char *seen, const char *header_msg);
-struct lock_file {
- struct lock_file *next;
- int fd;
- pid_t owner;
- char on_list;
- char filename[PATH_MAX];
-};
-#define LOCK_DIE_ON_ERROR 1
-#define LOCK_NODEREF 2
-extern int unable_to_lock_error(const char *path, int err);
-extern void unable_to_lock_message(const char *path, int err,
- struct strbuf *buf);
-extern NORETURN void unable_to_lock_index_die(const char *path, int err);
-extern int hold_lock_file_for_update(struct lock_file *, const char *path, int);
-extern int hold_lock_file_for_append(struct lock_file *, const char *path, int);
-extern int commit_lock_file(struct lock_file *);
-extern int reopen_lock_file(struct lock_file *);
extern void update_index_if_able(struct index_state *, struct lock_file *);
extern int hold_locked_index(struct lock_file *, int);
extern void set_alternate_index_output(const char *);
-extern int close_lock_file(struct lock_file *);
-extern void rollback_lock_file(struct lock_file *);
+
extern int delete_ref(const char *, const unsigned char *sha1, int delopt);
/* Environment bits from configuration mechanism */
extern const char *git_etc_gitconfig(void);
extern int check_repository_format_version(const char *var, const char *value, void *cb);
extern int git_env_bool(const char *, int);
+extern unsigned long git_env_ulong(const char *, unsigned long);
extern int git_config_system(void);
extern int config_error_nonbool(const char *);
#if defined(__GNUC__)
free(buf);
}
-static struct {
- char result;
- const char *check;
-} sigcheck_gpg_status[] = {
- { 'G', "\n[GNUPG:] GOODSIG " },
- { 'B', "\n[GNUPG:] BADSIG " },
- { 'U', "\n[GNUPG:] TRUST_NEVER" },
- { 'U', "\n[GNUPG:] TRUST_UNDEFINED" },
-};
-
-static void parse_gpg_output(struct signature_check *sigc)
-{
- const char *buf = sigc->gpg_status;
- int i;
-
- /* Iterate over all search strings */
- for (i = 0; i < ARRAY_SIZE(sigcheck_gpg_status); i++) {
- const char *found, *next;
-
- if (!skip_prefix(buf, sigcheck_gpg_status[i].check + 1, &found)) {
- found = strstr(buf, sigcheck_gpg_status[i].check);
- if (!found)
- continue;
- found += strlen(sigcheck_gpg_status[i].check);
- }
- sigc->result = sigcheck_gpg_status[i].result;
- /* The trust messages are not followed by key/signer information */
- if (sigc->result != 'U') {
- sigc->key = xmemdupz(found, 16);
- found += 17;
- next = strchrnul(found, '\n');
- sigc->signer = xmemdupz(found, next - found);
- }
- }
-}
-
void check_commit_signature(const struct commit *commit, struct signature_check *sigc)
{
struct strbuf payload = STRBUF_INIT;
*
*/
#include "cache.h"
+#include "lockfile.h"
#include "exec_cmd.h"
#include "strbuf.h"
#include "quote.h"
return system_wide;
}
+/*
+ * Parse environment variable 'k' as a boolean (in various
+ * possible spellings); if missing, use the default value 'def'.
+ */
int git_env_bool(const char *k, int def)
{
const char *v = getenv(k);
return v ? git_config_bool(k, v) : def;
}
+/*
+ * Parse environment variable 'k' as ulong with possibly a unit
+ * suffix; if missing, use the default value 'val'.
+ */
+unsigned long git_env_ulong(const char *k, unsigned long val)
+{
+ const char *v = getenv(k);
+ if (v && !git_parse_ulong(v, &val))
+ die("failed to parse %s", k);
+ return val;
+}
+
int git_config_system(void)
{
return !git_env_bool("GIT_CONFIG_NOSYSTEM", 0);
MAP_PRIVATE, in_fd, 0);
close(in_fd);
- if (chmod(lock->filename, st.st_mode & 07777) < 0) {
+ if (chmod(lock->filename.buf, st.st_mode & 07777) < 0) {
error("chmod on %s failed: %s",
- lock->filename, strerror(errno));
+ lock->filename.buf, strerror(errno));
ret = CONFIG_NO_WRITE;
goto out_free;
}
if (commit_lock_file(lock) < 0) {
error("could not commit config file %s", config_filename);
ret = CONFIG_NO_WRITE;
+ lock = NULL;
goto out_free;
}
return ret;
write_err_out:
- ret = write_error(lock->filename);
+ ret = write_error(lock->filename.buf);
goto out_free;
}
fstat(fileno(config_file), &st);
- if (chmod(lock->filename, st.st_mode & 07777) < 0) {
+ if (chmod(lock->filename.buf, st.st_mode & 07777) < 0) {
ret = error("chmod on %s failed: %s",
- lock->filename, strerror(errno));
+ lock->filename.buf, strerror(errno));
goto out;
}
}
store.baselen = strlen(new_name);
if (!store_write_section(out_fd, new_name)) {
- ret = write_error(lock->filename);
+ ret = write_error(lock->filename.buf);
goto out;
}
/*
continue;
length = strlen(output);
if (write_in_full(out_fd, output, length) != length) {
- ret = write_error(lock->filename);
+ ret = write_error(lock->filename.buf);
goto out;
}
}
--abbrev-commit --abbrev=
--relative-date --date=
--pretty= --format= --oneline
+ --show-signature
--cherry-pick
--graph
--decorate --decorate=
;;
--*)
__gitcomp "--pretty= --format= --abbrev-commit --oneline
+ --show-signature
$__git_diff_common_options
"
return
struct filter_params {
const char *src;
unsigned long size;
+ int fd;
const char *cmd;
const char *path;
};
-static int filter_buffer(int in, int out, void *data)
+static int filter_buffer_or_fd(int in, int out, void *data)
{
/*
* Spawn cmd and feed the buffer contents through its stdin.
sigchain_push(SIGPIPE, SIG_IGN);
- write_err = (write_in_full(child_process.in, params->src, params->size) < 0);
+ if (params->src) {
+ write_err = (write_in_full(child_process.in, params->src, params->size) < 0);
+ } else {
+ write_err = copy_fd(params->fd, child_process.in);
+ }
+
if (close(child_process.in))
write_err = 1;
if (write_err)
return (write_err || status);
}
-static int apply_filter(const char *path, const char *src, size_t len,
+static int apply_filter(const char *path, const char *src, size_t len, int fd,
struct strbuf *dst, const char *cmd)
{
/*
return 1;
memset(&async, 0, sizeof(async));
- async.proc = filter_buffer;
+ async.proc = filter_buffer_or_fd;
async.data = ¶ms;
async.out = -1;
params.src = src;
params.size = len;
+ params.fd = fd;
params.cmd = cmd;
params.path = path;
}
}
+int would_convert_to_git_filter_fd(const char *path)
+{
+ struct conv_attrs ca;
+
+ convert_attrs(&ca, path);
+ if (!ca.drv)
+ return 0;
+
+ /*
+ * Apply a filter to an fd only if the filter is required to succeed.
+ * We must die if the filter fails, because the original data before
+ * filtering is not available.
+ */
+ if (!ca.drv->required)
+ return 0;
+
+ return apply_filter(path, NULL, 0, -1, NULL, ca.drv->clean);
+}
+
int convert_to_git(const char *path, const char *src, size_t len,
struct strbuf *dst, enum safe_crlf checksafe)
{
required = ca.drv->required;
}
- ret |= apply_filter(path, src, len, dst, filter);
+ ret |= apply_filter(path, src, len, -1, dst, filter);
if (!ret && required)
die("%s: clean filter '%s' failed", path, ca.drv->name);
return ret | ident_to_git(path, src, len, dst, ca.ident);
}
+void convert_to_git_filter_fd(const char *path, int fd, struct strbuf *dst,
+ enum safe_crlf checksafe)
+{
+ struct conv_attrs ca;
+ convert_attrs(&ca, path);
+
+ assert(ca.drv);
+ assert(ca.drv->clean);
+
+ if (!apply_filter(path, NULL, 0, fd, dst, ca.drv->clean))
+ die("%s: clean filter '%s' failed", path, ca.drv->name);
+
+ ca.crlf_action = input_crlf_action(ca.crlf_action, ca.eol_attr);
+ crlf_to_git(path, dst->buf, dst->len, dst, ca.crlf_action, checksafe);
+ ident_to_git(path, dst->buf, dst->len, dst, ca.ident);
+}
+
static int convert_to_working_tree_internal(const char *path, const char *src,
size_t len, struct strbuf *dst,
int normalizing)
}
}
- ret_filter = apply_filter(path, src, len, dst, filter);
+ ret_filter = apply_filter(path, src, len, -1, dst, filter);
if (!ret_filter && required)
die("%s: smudge filter %s failed", path, ca.drv->name);
size_t len, struct strbuf *dst);
extern int renormalize_buffer(const char *path, const char *src, size_t len,
struct strbuf *dst);
-static inline int would_convert_to_git(const char *path, const char *src,
- size_t len, enum safe_crlf checksafe)
+static inline int would_convert_to_git(const char *path)
{
- return convert_to_git(path, src, len, NULL, checksafe);
+ return convert_to_git(path, NULL, 0, NULL, 0);
}
+/* Precondition: would_convert_to_git_filter_fd(path) == true */
+extern void convert_to_git_filter_fd(const char *path, int fd,
+ struct strbuf *dst,
+ enum safe_crlf checksafe);
+extern int would_convert_to_git_filter_fd(const char *path);
/*****************************************************************
*
{
while (1) {
char buffer[8192];
- char *buf = buffer;
ssize_t len = xread(ifd, buffer, sizeof(buffer));
if (!len)
break;
if (len < 0) {
- int read_error = errno;
- close(ifd);
return error("copy-fd: read returned %s",
- strerror(read_error));
- }
- while (len) {
- int written = xwrite(ofd, buf, len);
- if (written > 0) {
- buf += written;
- len -= written;
- }
- else if (!written) {
- close(ifd);
- return error("copy-fd: write returned 0");
- } else {
- int write_error = errno;
- close(ifd);
- return error("copy-fd: write returned %s",
- strerror(write_error));
- }
+ strerror(errno));
}
+ if (write_in_full(ofd, buffer, len) < 0)
+ return error("copy-fd: write returned %s",
+ strerror(errno));
}
- close(ifd);
return 0;
}
return fdo;
}
status = copy_fd(fdi, fdo);
+ close(fdi);
if (close(fdo) != 0)
return error("%s: close error: %s", dst, strerror(errno));
#include "cache.h"
+#include "lockfile.h"
#include "credential.h"
#include "string-list.h"
#include "parse-options.h"
static char addrbuf[HOST_NAME_MAX + 1];
hent = gethostbyname(hostname);
+ if (hent) {
+ ap = hent->h_addr_list;
+ memset(&sa, 0, sizeof sa);
+ sa.sin_family = hent->h_addrtype;
+ sa.sin_port = htons(0);
+ memcpy(&sa.sin_addr, *ap, hent->h_length);
+
+ inet_ntop(hent->h_addrtype, &sa.sin_addr,
+ addrbuf, sizeof(addrbuf));
- ap = hent->h_addr_list;
- memset(&sa, 0, sizeof sa);
- sa.sin_family = hent->h_addrtype;
- sa.sin_port = htons(0);
- memcpy(&sa.sin_addr, *ap, hent->h_length);
-
- inet_ntop(hent->h_addrtype, &sa.sin_addr,
- addrbuf, sizeof(addrbuf));
-
- free(canon_hostname);
- canon_hostname = xstrdup(hent->h_name);
- free(ip_address);
- ip_address = xstrdup(addrbuf);
+ free(canon_hostname);
+ canon_hostname = xstrdup(hent->h_name);
+ free(ip_address);
+ ip_address = xstrdup(addrbuf);
+ }
#endif
}
}
static int setup_named_sock(char *listen_addr, int listen_port, struct socketlist *socklist)
{
int socknum = 0;
- int maxfd = -1;
char pbuf[NI_MAXSERV];
struct addrinfo hints, *ai0, *ai;
int gai;
ALLOC_GROW(socklist->list, socklist->nr + 1, socklist->alloc);
socklist->list[socklist->nr++] = sockfd;
socknum++;
-
- if (maxfd < sockfd)
- maxfd = sockfd;
}
freeaddrinfo(ai0);
}
if ( bind(sockfd, (struct sockaddr *)&sin, sizeof sin) < 0 ) {
- logerror("Could not listen to %s: %s",
+ logerror("Could not bind to %s: %s",
ip2str(AF_INET, (struct sockaddr *)&sin, sizeof(sin)),
strerror(errno));
close(sockfd);
#include "builtin.h"
#include "cache.h"
+#include "lockfile.h"
#include "object.h"
#include "blob.h"
#include "tree.h"
static void dump_marks(void)
{
static struct lock_file mark_lock;
- int mark_fd;
FILE *f;
if (!export_marks_file)
return;
- mark_fd = hold_lock_file_for_update(&mark_lock, export_marks_file, 0);
- if (mark_fd < 0) {
+ if (hold_lock_file_for_update(&mark_lock, export_marks_file, 0) < 0) {
failure |= error("Unable to write marks file %s: %s",
export_marks_file, strerror(errno));
return;
}
- f = fdopen(mark_fd, "w");
+ f = fdopen_lock_file(&mark_lock, "w");
if (!f) {
int saved_errno = errno;
rollback_lock_file(&mark_lock);
return;
}
- /*
- * Since the lock file was fdopen()'ed, it should not be close()'ed.
- * Assign -1 to the lock file descriptor so that commit_lock_file()
- * won't try to close() it.
- */
- mark_lock.fd = -1;
-
dump_marks_helper(f, 0, marks);
- if (ferror(f) || fclose(f)) {
- int saved_errno = errno;
- rollback_lock_file(&mark_lock);
- failure |= error("Unable to write marks file %s: %s",
- export_marks_file, strerror(saved_errno));
- return;
- }
-
if (commit_lock_file(&mark_lock)) {
- int saved_errno = errno;
- rollback_lock_file(&mark_lock);
failure |= error("Unable to commit marks file %s: %s",
- export_marks_file, strerror(saved_errno));
+ export_marks_file, strerror(errno));
return;
}
}
#include "cache.h"
+#include "lockfile.h"
#include "refs.h"
#include "pkt-line.h"
#include "commit.h"
static char *configured_signing_key;
static const char *gpg_program = "gpg";
+#define PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----"
+#define PGP_MESSAGE "-----BEGIN PGP MESSAGE-----"
+
void signature_check_clear(struct signature_check *sigc)
{
free(sigc->payload);
sigc->key = NULL;
}
+static struct {
+ char result;
+ const char *check;
+} sigcheck_gpg_status[] = {
+ { 'G', "\n[GNUPG:] GOODSIG " },
+ { 'B', "\n[GNUPG:] BADSIG " },
+ { 'U', "\n[GNUPG:] TRUST_NEVER" },
+ { 'U', "\n[GNUPG:] TRUST_UNDEFINED" },
+};
+
+void parse_gpg_output(struct signature_check *sigc)
+{
+ const char *buf = sigc->gpg_status;
+ int i;
+
+ /* Iterate over all search strings */
+ for (i = 0; i < ARRAY_SIZE(sigcheck_gpg_status); i++) {
+ const char *found, *next;
+
+ if (!skip_prefix(buf, sigcheck_gpg_status[i].check + 1, &found)) {
+ found = strstr(buf, sigcheck_gpg_status[i].check);
+ if (!found)
+ continue;
+ found += strlen(sigcheck_gpg_status[i].check);
+ }
+ sigc->result = sigcheck_gpg_status[i].result;
+ /* The trust messages are not followed by key/signer information */
+ if (sigc->result != 'U') {
+ sigc->key = xmemdupz(found, 16);
+ found += 17;
+ next = strchrnul(found, '\n');
+ sigc->signer = xmemdupz(found, next - found);
+ }
+ }
+}
+
+/*
+ * Look at GPG signed content (e.g. a signed tag object), whose
+ * payload is followed by a detached signature on it. Return the
+ * offset where the embedded detached signature begins, or the end of
+ * the data when there is no such signature.
+ */
+size_t parse_signature(const char *buf, unsigned long size)
+{
+ char *eol;
+ size_t len = 0;
+ while (len < size && !starts_with(buf + len, PGP_SIGNATURE) &&
+ !starts_with(buf + len, PGP_MESSAGE)) {
+ eol = memchr(buf + len, '\n', size - len);
+ len += eol ? eol - (buf + len) + 1 : size - len;
+ }
+ return len;
+}
+
void set_signing_key(const char *key)
{
free(configured_signing_key);
char *payload;
char *gpg_output;
char *gpg_status;
- char result; /* 0 (not checked),
- * N (checked but no further result),
- * U (untrusted good),
- * G (good)
- * B (bad) */
+
+ /*
+ * possible "result":
+ * 0 (not checked)
+ * N (checked but no further result)
+ * U (untrusted good)
+ * G (good)
+ * B (bad)
+ */
+ char result;
char *signer;
char *key;
};
extern void signature_check_clear(struct signature_check *sigc);
+extern size_t parse_signature(const char *buf, unsigned long size);
+extern void parse_gpg_output(struct signature_check *);
extern int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *signing_key);
extern int verify_signed_buffer(const char *payload, size_t payload_size, const char *signature, size_t signature_size, struct strbuf *gpg_output, struct strbuf *gpg_status);
extern int git_gpg_config(const char *, const char *, void *);
+#include "git-compat-util.h"
#include "http.h"
#include "pack.h"
#include "sideband.h"
* Copyright (c) 2005, Junio C Hamano
*/
#include "cache.h"
+#include "lockfile.h"
#include "sigchain.h"
-static struct lock_file *lock_file_list;
+static struct lock_file *volatile lock_file_list;
-static void remove_lock_file(void)
+static void remove_lock_files(int skip_fclose)
{
pid_t me = getpid();
while (lock_file_list) {
- if (lock_file_list->owner == me &&
- lock_file_list->filename[0]) {
- if (lock_file_list->fd >= 0)
- close(lock_file_list->fd);
- unlink_or_warn(lock_file_list->filename);
+ if (lock_file_list->owner == me) {
+ /* fclose() is not safe to call in a signal handler */
+ if (skip_fclose)
+ lock_file_list->fp = NULL;
+ rollback_lock_file(lock_file_list);
}
lock_file_list = lock_file_list->next;
}
}
-static void remove_lock_file_on_signal(int signo)
+static void remove_lock_files_on_exit(void)
{
- remove_lock_file();
+ remove_lock_files(0);
+}
+
+static void remove_lock_files_on_signal(int signo)
+{
+ remove_lock_files(1);
sigchain_pop(signo);
raise(signo);
}
/*
- * p = absolute or relative path name
+ * path = absolute or relative path name
*
- * Return a pointer into p showing the beginning of the last path name
- * element. If p is empty or the root directory ("/"), just return p.
+ * Remove the last path name element from path (leaving the preceding
+ * "/", if any). If path is empty or the root directory ("/"), set
+ * path to the empty string.
*/
-static char *last_path_elm(char *p)
+static void trim_last_path_component(struct strbuf *path)
{
- /* r starts pointing to null at the end of the string */
- char *r = strchr(p, '\0');
-
- if (r == p)
- return p; /* just return empty string */
-
- r--; /* back up to last non-null character */
+ int i = path->len;
/* back up past trailing slashes, if any */
- while (r > p && *r == '/')
- r--;
+ while (i && path->buf[i - 1] == '/')
+ i--;
/*
- * then go backwards until I hit a slash, or the beginning of
- * the string
+ * then go backwards until a slash, or the beginning of the
+ * string
*/
- while (r > p && *(r-1) != '/')
- r--;
- return r;
+ while (i && path->buf[i - 1] != '/')
+ i--;
+
+ strbuf_setlen(path, i);
}
#define MAXDEPTH 5
/*
- * p = path that may be a symlink
- * s = full size of p
- *
- * If p is a symlink, attempt to overwrite p with a path to the real
- * file or directory (which may or may not exist), following a chain of
- * symlinks if necessary. Otherwise, leave p unmodified.
+ * path contains a path that might be a symlink.
*
- * This is a best-effort routine. If an error occurs, p will either be
- * left unmodified or will name a different symlink in a symlink chain
- * that started with p's initial contents.
+ * If path is a symlink, attempt to overwrite it with a path to the
+ * real file or directory (which may or may not exist), following a
+ * chain of symlinks if necessary. Otherwise, leave path unmodified.
*
- * Always returns p.
+ * This is a best-effort routine. If an error occurs, path will
+ * either be left unmodified or will name a different symlink in a
+ * symlink chain that started with the original path.
*/
-
-static char *resolve_symlink(char *p, size_t s)
+static void resolve_symlink(struct strbuf *path)
{
int depth = MAXDEPTH;
+ static struct strbuf link = STRBUF_INIT;
while (depth--) {
- char link[PATH_MAX];
- int link_len = readlink(p, link, sizeof(link));
- if (link_len < 0) {
- /* not a symlink anymore */
- return p;
- }
- else if (link_len < sizeof(link))
- /* readlink() never null-terminates */
- link[link_len] = '\0';
- else {
- warning("%s: symlink too long", p);
- return p;
- }
+ if (strbuf_readlink(&link, path->buf, path->len) < 0)
+ break;
- if (is_absolute_path(link)) {
+ if (is_absolute_path(link.buf))
/* absolute path simply replaces p */
- if (link_len < s)
- strcpy(p, link);
- else {
- warning("%s: symlink too long", p);
- return p;
- }
- } else {
+ strbuf_reset(path);
+ else
/*
- * link is a relative path, so I must replace the
+ * link is a relative path, so replace the
* last element of p with it.
*/
- char *r = (char *)last_path_elm(p);
- if (r - p + link_len < s)
- strcpy(r, link);
- else {
- warning("%s: symlink too long", p);
- return p;
- }
- }
+ trim_last_path_component(path);
+
+ strbuf_addbuf(path, &link);
}
- return p;
+ strbuf_reset(&link);
}
/* Make sure errno contains a meaningful value on error */
static int lock_file(struct lock_file *lk, const char *path, int flags)
{
- /*
- * subtract 5 from size to make sure there's room for adding
- * ".lock" for the lock file name
- */
- static const size_t max_path_len = sizeof(lk->filename) - 5;
+ size_t pathlen = strlen(path);
- if (strlen(path) >= max_path_len) {
- errno = ENAMETOOLONG;
+ if (!lock_file_list) {
+ /* One-time initialization */
+ sigchain_push_common(remove_lock_files_on_signal);
+ atexit(remove_lock_files_on_exit);
+ }
+
+ if (lk->active)
+ die("BUG: cannot lock_file(\"%s\") using active struct lock_file",
+ path);
+ if (!lk->on_list) {
+ /* Initialize *lk and add it to lock_file_list: */
+ lk->fd = -1;
+ lk->fp = NULL;
+ lk->active = 0;
+ lk->owner = 0;
+ strbuf_init(&lk->filename, pathlen + LOCK_SUFFIX_LEN);
+ lk->next = lock_file_list;
+ lock_file_list = lk;
+ lk->on_list = 1;
+ } else if (lk->filename.len) {
+ /* This shouldn't happen, but better safe than sorry. */
+ die("BUG: lock_file(\"%s\") called with improperly-reset lock_file object",
+ path);
+ }
+
+ strbuf_add(&lk->filename, path, pathlen);
+ if (!(flags & LOCK_NO_DEREF))
+ resolve_symlink(&lk->filename);
+ strbuf_addstr(&lk->filename, LOCK_SUFFIX);
+ lk->fd = open(lk->filename.buf, O_RDWR | O_CREAT | O_EXCL, 0666);
+ if (lk->fd < 0) {
+ strbuf_reset(&lk->filename);
return -1;
}
- strcpy(lk->filename, path);
- if (!(flags & LOCK_NODEREF))
- resolve_symlink(lk->filename, max_path_len);
- strcat(lk->filename, ".lock");
- lk->fd = open(lk->filename, O_RDWR | O_CREAT | O_EXCL, 0666);
- if (0 <= lk->fd) {
- if (!lock_file_list) {
- sigchain_push_common(remove_lock_file_on_signal);
- atexit(remove_lock_file);
- }
- lk->owner = getpid();
- if (!lk->on_list) {
- lk->next = lock_file_list;
- lock_file_list = lk;
- lk->on_list = 1;
- }
- if (adjust_shared_perm(lk->filename)) {
- int save_errno = errno;
- error("cannot fix permission bits on %s",
- lk->filename);
- errno = save_errno;
- return -1;
- }
+ lk->owner = getpid();
+ lk->active = 1;
+ if (adjust_shared_perm(lk->filename.buf)) {
+ int save_errno = errno;
+ error("cannot fix permission bits on %s", lk->filename.buf);
+ rollback_lock_file(lk);
+ errno = save_errno;
+ return -1;
}
- else
- lk->filename[0] = 0;
return lk->fd;
}
return -1;
}
-NORETURN void unable_to_lock_index_die(const char *path, int err)
+NORETURN void unable_to_lock_die(const char *path, int err)
{
struct strbuf buf = STRBUF_INIT;
{
int fd = lock_file(lk, path, flags);
if (fd < 0 && (flags & LOCK_DIE_ON_ERROR))
- unable_to_lock_index_die(path, errno);
+ unable_to_lock_die(path, errno);
return fd;
}
fd = lock_file(lk, path, flags);
if (fd < 0) {
if (flags & LOCK_DIE_ON_ERROR)
- unable_to_lock_index_die(path, errno);
+ unable_to_lock_die(path, errno);
return fd;
}
orig_fd = open(path, O_RDONLY);
if (orig_fd < 0) {
if (errno != ENOENT) {
+ int save_errno = errno;
+
if (flags & LOCK_DIE_ON_ERROR)
die("cannot open '%s' for copying", path);
- close(fd);
- return error("cannot open '%s' for copying", path);
+ rollback_lock_file(lk);
+ error("cannot open '%s' for copying", path);
+ errno = save_errno;
+ return -1;
}
} else if (copy_fd(orig_fd, fd)) {
+ int save_errno = errno;
+
if (flags & LOCK_DIE_ON_ERROR)
exit(128);
- close(fd);
+ close(orig_fd);
+ rollback_lock_file(lk);
+ errno = save_errno;
return -1;
+ } else {
+ close(orig_fd);
}
return fd;
}
+FILE *fdopen_lock_file(struct lock_file *lk, const char *mode)
+{
+ if (!lk->active)
+ die("BUG: fdopen_lock_file() called for unlocked object");
+ if (lk->fp)
+ die("BUG: fdopen_lock_file() called twice for file '%s'", lk->filename.buf);
+
+ lk->fp = fdopen(lk->fd, mode);
+ return lk->fp;
+}
+
+char *get_locked_file_path(struct lock_file *lk)
+{
+ if (!lk->active)
+ die("BUG: get_locked_file_path() called for unlocked object");
+ if (lk->filename.len <= LOCK_SUFFIX_LEN)
+ die("BUG: get_locked_file_path() called for malformed lock object");
+ return xmemdupz(lk->filename.buf, lk->filename.len - LOCK_SUFFIX_LEN);
+}
+
int close_lock_file(struct lock_file *lk)
{
int fd = lk->fd;
+ FILE *fp = lk->fp;
+ int err;
+
+ if (fd < 0)
+ return 0;
+
lk->fd = -1;
- return close(fd);
+ if (fp) {
+ lk->fp = NULL;
+
+ /*
+ * Note: no short-circuiting here; we want to fclose()
+ * in any case!
+ */
+ err = ferror(fp) | fclose(fp);
+ } else {
+ err = close(fd);
+ }
+
+ if (err) {
+ int save_errno = errno;
+ rollback_lock_file(lk);
+ errno = save_errno;
+ return -1;
+ }
+
+ return 0;
}
int reopen_lock_file(struct lock_file *lk)
{
if (0 <= lk->fd)
die(_("BUG: reopen a lockfile that is still open"));
- if (!lk->filename[0])
+ if (!lk->active)
die(_("BUG: reopen a lockfile that has been committed"));
- lk->fd = open(lk->filename, O_WRONLY);
+ lk->fd = open(lk->filename.buf, O_WRONLY);
return lk->fd;
}
-int commit_lock_file(struct lock_file *lk)
+int commit_lock_file_to(struct lock_file *lk, const char *path)
{
- char result_file[PATH_MAX];
- size_t i;
- if (lk->fd >= 0 && close_lock_file(lk))
+ if (!lk->active)
+ die("BUG: attempt to commit unlocked object to \"%s\"", path);
+
+ if (close_lock_file(lk))
return -1;
- strcpy(result_file, lk->filename);
- i = strlen(result_file) - 5; /* .lock */
- result_file[i] = 0;
- if (rename(lk->filename, result_file))
+
+ if (rename(lk->filename.buf, path)) {
+ int save_errno = errno;
+ rollback_lock_file(lk);
+ errno = save_errno;
return -1;
- lk->filename[0] = 0;
+ }
+
+ lk->active = 0;
+ strbuf_reset(&lk->filename);
return 0;
}
-int hold_locked_index(struct lock_file *lk, int die_on_error)
+int commit_lock_file(struct lock_file *lk)
{
- return hold_lock_file_for_update(lk, get_index_file(),
- die_on_error
- ? LOCK_DIE_ON_ERROR
- : 0);
+ static struct strbuf result_file = STRBUF_INIT;
+ int err;
+
+ if (!lk->active)
+ die("BUG: attempt to commit unlocked object");
+
+ if (lk->filename.len <= LOCK_SUFFIX_LEN ||
+ strcmp(lk->filename.buf + lk->filename.len - LOCK_SUFFIX_LEN, LOCK_SUFFIX))
+ die("BUG: lockfile filename corrupt");
+
+ /* remove ".lock": */
+ strbuf_add(&result_file, lk->filename.buf,
+ lk->filename.len - LOCK_SUFFIX_LEN);
+ err = commit_lock_file_to(lk, result_file.buf);
+ strbuf_reset(&result_file);
+ return err;
}
void rollback_lock_file(struct lock_file *lk)
{
- if (lk->filename[0]) {
- if (lk->fd >= 0)
- close(lk->fd);
- unlink_or_warn(lk->filename);
+ if (!lk->active)
+ return;
+
+ if (!close_lock_file(lk)) {
+ unlink_or_warn(lk->filename.buf);
+ lk->active = 0;
+ strbuf_reset(&lk->filename);
}
- lk->filename[0] = 0;
}
--- /dev/null
+#ifndef LOCKFILE_H
+#define LOCKFILE_H
+
+/*
+ * File write-locks as used by Git.
+ *
+ * For an overview of how to use the lockfile API, please see
+ *
+ * Documentation/technical/api-lockfile.txt
+ *
+ * This module keeps track of all locked files in lock_file_list for
+ * use at cleanup. This list and the lock_file objects that comprise
+ * it must be kept in self-consistent states at all time, because the
+ * program can be interrupted any time by a signal, in which case the
+ * signal handler will walk through the list attempting to clean up
+ * any open lock files.
+ *
+ * A lockfile is owned by the process that created it. The lock_file
+ * object has an "owner" field that records its owner. This field is
+ * used to prevent a forked process from closing a lockfile created by
+ * its parent.
+ *
+ * The possible states of a lock_file object are as follows:
+ *
+ * - Uninitialized. In this state the object's on_list field must be
+ * zero but the rest of its contents need not be initialized. As
+ * soon as the object is used in any way, it is irrevocably
+ * registered in the lock_file_list, and on_list is set.
+ *
+ * - Locked, lockfile open (after hold_lock_file_for_update(),
+ * hold_lock_file_for_append(), or reopen_lock_file()). In this
+ * state:
+ * - the lockfile exists
+ * - active is set
+ * - filename holds the filename of the lockfile
+ * - fd holds a file descriptor open for writing to the lockfile
+ * - fp holds a pointer to an open FILE object if and only if
+ * fdopen_lock_file() has been called on the object
+ * - owner holds the PID of the process that locked the file
+ *
+ * - Locked, lockfile closed (after successful close_lock_file()).
+ * Same as the previous state, except that the lockfile is closed
+ * and fd is -1.
+ *
+ * - Unlocked (after commit_lock_file(), commit_lock_file_to(),
+ * rollback_lock_file(), a failed attempt to lock, or a failed
+ * close_lock_file()). In this state:
+ * - active is unset
+ * - filename is empty (usually, though there are transitory
+ * states in which this condition doesn't hold). Client code should
+ * *not* rely on the filename being empty in this state.
+ * - fd is -1
+ * - the object is left registered in the lock_file_list, and
+ * on_list is set.
+ */
+
+struct lock_file {
+ struct lock_file *volatile next;
+ volatile sig_atomic_t active;
+ volatile int fd;
+ FILE *volatile fp;
+ volatile pid_t owner;
+ char on_list;
+ struct strbuf filename;
+};
+
+/* String appended to a filename to derive the lockfile name: */
+#define LOCK_SUFFIX ".lock"
+#define LOCK_SUFFIX_LEN 5
+
+#define LOCK_DIE_ON_ERROR 1
+#define LOCK_NO_DEREF 2
+
+extern int unable_to_lock_error(const char *path, int err);
+extern void unable_to_lock_message(const char *path, int err,
+ struct strbuf *buf);
+extern NORETURN void unable_to_lock_die(const char *path, int err);
+extern int hold_lock_file_for_update(struct lock_file *, const char *path, int);
+extern int hold_lock_file_for_append(struct lock_file *, const char *path, int);
+extern FILE *fdopen_lock_file(struct lock_file *, const char *mode);
+extern char *get_locked_file_path(struct lock_file *);
+extern int commit_lock_file_to(struct lock_file *, const char *path);
+extern int commit_lock_file(struct lock_file *);
+extern int reopen_lock_file(struct lock_file *);
+extern int close_lock_file(struct lock_file *);
+extern void rollback_lock_file(struct lock_file *);
+
+#endif /* LOCKFILE_H */
* Fredrik Kuivinen.
* The thieves were Alex Riesen and Johannes Schindelin, in June/July 2006
*/
-#include "advice.h"
#include "cache.h"
+#include "advice.h"
+#include "lockfile.h"
#include "cache-tree.h"
#include "commit.h"
#include "blob.h"
#include "cache.h"
+#include "lockfile.h"
#include "commit.h"
#include "run-command.h"
#include "resolve-undo.h"
*/
#define NO_THE_INDEX_COMPATIBILITY_MACROS
#include "cache.h"
+#include "lockfile.h"
#include "cache-tree.h"
#include "refs.h"
#include "dir.h"
return 0;
}
+int hold_locked_index(struct lock_file *lk, int die_on_error)
+{
+ return hold_lock_file_for_update(lk, get_index_file(),
+ die_on_error
+ ? LOCK_DIE_ON_ERROR
+ : 0);
+}
+
int read_index(struct index_state *istate)
{
return read_index_from(istate, get_index_file());
static int commit_locked_index(struct lock_file *lk)
{
- if (alternate_index_output) {
- if (lk->fd >= 0 && close_lock_file(lk))
- return -1;
- if (rename(lk->filename, alternate_index_output))
- return -1;
- lk->filename[0] = 0;
- return 0;
- } else {
+ if (alternate_index_output)
+ return commit_lock_file_to(lk, alternate_index_output);
+ else
return commit_lock_file(lk);
- }
}
static int do_write_locked_index(struct index_state *istate, struct lock_file *lock,
#include "cache.h"
+#include "lockfile.h"
#include "refs.h"
#include "object.h"
#include "tag.h"
if (refname[1] == '\0')
return -1; /* Component equals ".". */
}
- if (cp - refname >= 5 && !memcmp(cp - 5, ".lock", 5))
+ if (cp - refname >= LOCK_SUFFIX_LEN &&
+ !memcmp(cp - LOCK_SUFFIX_LEN, LOCK_SUFFIX, LOCK_SUFFIX_LEN))
return -1; /* Refname ends with ".lock". */
return cp - refname;
}
lflags = 0;
if (flags & REF_NODEREF) {
refname = orig_refname;
- lflags |= LOCK_NODEREF;
+ lflags |= LOCK_NO_DEREF;
}
lock->ref_name = xstrdup(refname);
lock->orig_ref_name = xstrdup(orig_refname);
*/
goto retry;
else
- unable_to_lock_index_die(ref_file, errno);
+ unable_to_lock_die(ref_file, errno);
}
return old_sha1 ? verify_lock(lock, old_sha1, mustexist) : lock;
if (!packed_ref_cache->lock)
die("internal error: packed-refs not locked");
- out = fdopen(packed_ref_cache->lock->fd, "w");
+ out = fdopen_lock_file(packed_ref_cache->lock, "w");
if (!out)
die_errno("unable to fdopen packed-refs descriptor");
fprintf_or_die(out, "%s", PACKED_REFS_HEADER);
do_for_each_entry_in_dir(get_packed_ref_dir(packed_ref_cache),
0, write_packed_entry_fn, out);
- if (fclose(out))
- die_errno("write error");
- packed_ref_cache->lock->fd = -1;
if (commit_lock_file(packed_ref_cache->lock)) {
save_errno = errno;
static int delete_ref_loose(struct ref_lock *lock, int flag)
{
if (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) {
- /* loose */
- int err, i = strlen(lock->lk->filename) - 5; /* .lock */
-
- lock->lk->filename[i] = 0;
- err = unlink_or_warn(lock->lk->filename);
- lock->lk->filename[i] = '.';
+ /*
+ * loose. The loose file name is the same as the
+ * lockfile name, minus ".lock":
+ */
+ char *loose_filename = get_locked_file_path(lock->lk);
+ int err = unlink_or_warn(loose_filename);
+ free(loose_filename);
if (err && errno != ENOENT)
return 1;
}
write_in_full(lock->lock_fd, &term, 1) != 1 ||
close_ref(lock) < 0) {
int save_errno = errno;
- error("Couldn't write %s", lock->lk->filename);
+ error("Couldn't write %s", lock->lk->filename.buf);
unlock_ref(lock);
errno = save_errno;
return -1;
update_shallow : 1,
followtags : 1,
dry_run : 1,
- thin : 1;
+ thin : 1,
+ push_cert : 1;
};
static struct options options;
static struct string_list cas_options = STRING_LIST_INIT_DUP;
else
return -1;
return 0;
+ } else if (!strcmp(name, "pushcert")) {
+ if (!strcmp(value, "true"))
+ options.push_cert = 1;
+ else if (!strcmp(value, "false"))
+ options.push_cert = 0;
+ else
+ return -1;
+ return 0;
} else {
return 1 /* unsupported */;
}
argv_array_push(&args, "--thin");
if (options.dry_run)
argv_array_push(&args, "--dry-run");
+ if (options.push_cert)
+ argv_array_push(&args, "--signed");
if (options.verbosity == 0)
argv_array_push(&args, "--quiet");
else if (options.verbosity > 1)
#include "cache.h"
+#include "lockfile.h"
#include "string-list.h"
#include "rerere.h"
#include "xdiff-interface.h"
#include "transport.h"
#include "version.h"
#include "sha1-array.h"
+#include "gpg-interface.h"
static int feed_object(const unsigned char *sha1, int fd, int negative)
{
for_each_commit_graft(advertise_shallow_grafts_cb, sb);
}
+static int ref_update_to_be_sent(const struct ref *ref, const struct send_pack_args *args)
+{
+ if (!ref->peer_ref && !args->send_mirror)
+ return 0;
+
+ /* Check for statuses set by set_ref_status_for_push() */
+ switch (ref->status) {
+ case REF_STATUS_REJECT_NONFASTFORWARD:
+ case REF_STATUS_REJECT_ALREADY_EXISTS:
+ case REF_STATUS_REJECT_FETCH_FIRST:
+ case REF_STATUS_REJECT_NEEDS_FORCE:
+ case REF_STATUS_REJECT_STALE:
+ case REF_STATUS_REJECT_NODELETE:
+ case REF_STATUS_UPTODATE:
+ return 0;
+ default:
+ return 1;
+ }
+}
+
+/*
+ * the beginning of the next line, or the end of buffer.
+ *
+ * NEEDSWORK: perhaps move this to git-compat-util.h or somewhere and
+ * convert many similar uses found by "git grep -A4 memchr".
+ */
+static const char *next_line(const char *line, size_t len)
+{
+ const char *nl = memchr(line, '\n', len);
+ if (!nl)
+ return line + len; /* incomplete line */
+ return nl + 1;
+}
+
+static int generate_push_cert(struct strbuf *req_buf,
+ const struct ref *remote_refs,
+ struct send_pack_args *args,
+ const char *cap_string,
+ const char *push_cert_nonce)
+{
+ const struct ref *ref;
+ char *signing_key = xstrdup(get_signing_key());
+ const char *cp, *np;
+ struct strbuf cert = STRBUF_INIT;
+ int update_seen = 0;
+
+ strbuf_addf(&cert, "certificate version 0.1\n");
+ strbuf_addf(&cert, "pusher %s ", signing_key);
+ datestamp(&cert);
+ strbuf_addch(&cert, '\n');
+ if (args->url && *args->url) {
+ char *anon_url = transport_anonymize_url(args->url);
+ strbuf_addf(&cert, "pushee %s\n", anon_url);
+ free(anon_url);
+ }
+ if (push_cert_nonce[0])
+ strbuf_addf(&cert, "nonce %s\n", push_cert_nonce);
+ strbuf_addstr(&cert, "\n");
+
+ for (ref = remote_refs; ref; ref = ref->next) {
+ if (!ref_update_to_be_sent(ref, args))
+ continue;
+ update_seen = 1;
+ strbuf_addf(&cert, "%s %s %s\n",
+ sha1_to_hex(ref->old_sha1),
+ sha1_to_hex(ref->new_sha1),
+ ref->name);
+ }
+ if (!update_seen)
+ goto free_return;
+
+ if (sign_buffer(&cert, &cert, signing_key))
+ die(_("failed to sign the push certificate"));
+
+ packet_buf_write(req_buf, "push-cert%c%s", 0, cap_string);
+ for (cp = cert.buf; cp < cert.buf + cert.len; cp = np) {
+ np = next_line(cp, cert.buf + cert.len - cp);
+ packet_buf_write(req_buf,
+ "%.*s", (int)(np - cp), cp);
+ }
+ packet_buf_write(req_buf, "push-cert-end\n");
+
+free_return:
+ free(signing_key);
+ strbuf_release(&cert);
+ return update_seen;
+}
+
int send_pack(struct send_pack_args *args,
int fd[], struct child_process *conn,
struct ref *remote_refs,
int in = fd[0];
int out = fd[1];
struct strbuf req_buf = STRBUF_INIT;
+ struct strbuf cap_buf = STRBUF_INIT;
struct ref *ref;
- int new_refs;
+ int need_pack_data = 0;
int allow_deleting_refs = 0;
int status_report = 0;
int use_sideband = 0;
unsigned cmds_sent = 0;
int ret;
struct async demux;
+ const char *push_cert_nonce = NULL;
/* Does the other end support the reporting? */
if (server_supports("report-status"))
agent_supported = 1;
if (server_supports("no-thin"))
args->use_thin_pack = 0;
+ if (args->push_cert) {
+ int len;
+
+ push_cert_nonce = server_feature_value("push-cert", &len);
+ if (!push_cert_nonce)
+ die(_("the receiving end does not support --signed push"));
+ push_cert_nonce = xmemdupz(push_cert_nonce, len);
+ }
if (!remote_refs) {
fprintf(stderr, "No refs in common and none specified; doing nothing.\n"
return 0;
}
+ if (status_report)
+ strbuf_addstr(&cap_buf, " report-status");
+ if (use_sideband)
+ strbuf_addstr(&cap_buf, " side-band-64k");
+ if (quiet_supported && (args->quiet || !args->progress))
+ strbuf_addstr(&cap_buf, " quiet");
+ if (agent_supported)
+ strbuf_addf(&cap_buf, " agent=%s", git_user_agent_sanitized());
+
+ /*
+ * NEEDSWORK: why does delete-refs have to be so specific to
+ * send-pack machinery that set_ref_status_for_push() cannot
+ * set this bit for us???
+ */
+ for (ref = remote_refs; ref; ref = ref->next)
+ if (ref->deletion && !allow_deleting_refs)
+ ref->status = REF_STATUS_REJECT_NODELETE;
+
if (!args->dry_run)
advertise_shallow_grafts_buf(&req_buf);
+ if (!args->dry_run && args->push_cert)
+ cmds_sent = generate_push_cert(&req_buf, remote_refs, args,
+ cap_buf.buf, push_cert_nonce);
+
/*
- * Finally, tell the other end!
+ * Clear the status for each ref and see if we need to send
+ * the pack data.
*/
- new_refs = 0;
for (ref = remote_refs; ref; ref = ref->next) {
- if (!ref->peer_ref && !args->send_mirror)
+ if (!ref_update_to_be_sent(ref, args))
continue;
- /* Check for statuses set by set_ref_status_for_push() */
- switch (ref->status) {
- case REF_STATUS_REJECT_NONFASTFORWARD:
- case REF_STATUS_REJECT_ALREADY_EXISTS:
- case REF_STATUS_REJECT_FETCH_FIRST:
- case REF_STATUS_REJECT_NEEDS_FORCE:
- case REF_STATUS_REJECT_STALE:
- case REF_STATUS_UPTODATE:
- continue;
- default:
- ; /* do nothing */
- }
+ if (!ref->deletion)
+ need_pack_data = 1;
- if (ref->deletion && !allow_deleting_refs) {
- ref->status = REF_STATUS_REJECT_NODELETE;
+ if (args->dry_run || !status_report)
+ ref->status = REF_STATUS_OK;
+ else
+ ref->status = REF_STATUS_EXPECTING_REPORT;
+ }
+
+ /*
+ * Finally, tell the other end!
+ */
+ for (ref = remote_refs; ref; ref = ref->next) {
+ char *old_hex, *new_hex;
+
+ if (args->dry_run || args->push_cert)
continue;
- }
- if (!ref->deletion)
- new_refs++;
+ if (!ref_update_to_be_sent(ref, args))
+ continue;
- if (args->dry_run) {
- ref->status = REF_STATUS_OK;
+ old_hex = sha1_to_hex(ref->old_sha1);
+ new_hex = sha1_to_hex(ref->new_sha1);
+ if (!cmds_sent) {
+ packet_buf_write(&req_buf,
+ "%s %s %s%c%s",
+ old_hex, new_hex, ref->name, 0,
+ cap_buf.buf);
+ cmds_sent = 1;
} else {
- char *old_hex = sha1_to_hex(ref->old_sha1);
- char *new_hex = sha1_to_hex(ref->new_sha1);
- int quiet = quiet_supported && (args->quiet || !args->progress);
-
- if (!cmds_sent && (status_report || use_sideband ||
- quiet || agent_supported)) {
- packet_buf_write(&req_buf,
- "%s %s %s%c%s%s%s%s%s",
- old_hex, new_hex, ref->name, 0,
- status_report ? " report-status" : "",
- use_sideband ? " side-band-64k" : "",
- quiet ? " quiet" : "",
- agent_supported ? " agent=" : "",
- agent_supported ? git_user_agent_sanitized() : ""
- );
- }
- else
- packet_buf_write(&req_buf, "%s %s %s",
- old_hex, new_hex, ref->name);
- ref->status = status_report ?
- REF_STATUS_EXPECTING_REPORT :
- REF_STATUS_OK;
- cmds_sent++;
+ packet_buf_write(&req_buf, "%s %s %s",
+ old_hex, new_hex, ref->name);
}
}
packet_flush(out);
}
strbuf_release(&req_buf);
+ strbuf_release(&cap_buf);
if (use_sideband && cmds_sent) {
memset(&demux, 0, sizeof(demux));
in = demux.out;
}
- if (new_refs && cmds_sent) {
+ if (need_pack_data && cmds_sent) {
if (pack_objects(out, remote_refs, extra_have, args) < 0) {
for (ref = remote_refs; ref; ref = ref->next)
ref->status = REF_STATUS_NONE;
#define SEND_PACK_H
struct send_pack_args {
+ const char *url;
unsigned verbose:1,
quiet:1,
porcelain:1,
use_thin_pack:1,
use_ofs_delta:1,
dry_run:1,
+ push_cert:1,
stateless_rpc:1;
};
#include "cache.h"
+#include "lockfile.h"
#include "sequencer.h"
#include "dir.h"
#include "object.h"
die("BUG: assertion failed in binary search");
}
}
- if (18 <= ofs)
- die("cannot happen -- lo and hi are identical");
}
do {
*/
#include "cache.h"
#include "string-list.h"
+#include "lockfile.h"
#include "delta.h"
#include "pack.h"
#include "blob.h"
; /* nothing */
}
+static void mmap_limit_check(size_t length)
+{
+ static size_t limit = 0;
+ if (!limit) {
+ limit = git_env_ulong("GIT_MMAP_LIMIT", 0);
+ if (!limit)
+ limit = SIZE_MAX;
+ }
+ if (length > limit)
+ die("attempting to mmap %"PRIuMAX" over limit %"PRIuMAX,
+ (uintmax_t)length, (uintmax_t)limit);
+}
+
void *xmmap(void *start, size_t length,
int prot, int flags, int fd, off_t offset)
{
- void *ret = mmap(start, length, prot, flags, fd, offset);
+ void *ret;
+
+ mmap_limit_check(length);
+ ret = mmap(start, length, prot, flags, fd, offset);
if (ret == MAP_FAILED) {
if (!length)
return NULL;
return ret;
}
+static int index_stream_convert_blob(unsigned char *sha1, int fd,
+ const char *path, unsigned flags)
+{
+ int ret;
+ const int write_object = flags & HASH_WRITE_OBJECT;
+ struct strbuf sbuf = STRBUF_INIT;
+
+ assert(path);
+ assert(would_convert_to_git_filter_fd(path));
+
+ convert_to_git_filter_fd(path, fd, &sbuf,
+ write_object ? safe_crlf : SAFE_CRLF_FALSE);
+
+ if (write_object)
+ ret = write_sha1_file(sbuf.buf, sbuf.len, typename(OBJ_BLOB),
+ sha1);
+ else
+ ret = hash_sha1_file(sbuf.buf, sbuf.len, typename(OBJ_BLOB),
+ sha1);
+ strbuf_release(&sbuf);
+ return ret;
+}
+
static int index_pipe(unsigned char *sha1, int fd, enum object_type type,
const char *path, unsigned flags)
{
enum object_type type, const char *path, unsigned flags)
{
int ret;
- size_t size = xsize_t(st->st_size);
- if (!S_ISREG(st->st_mode))
+ /*
+ * Call xsize_t() only when needed to avoid potentially unnecessary
+ * die() for large files.
+ */
+ if (type == OBJ_BLOB && path && would_convert_to_git_filter_fd(path))
+ ret = index_stream_convert_blob(sha1, fd, path, flags);
+ else if (!S_ISREG(st->st_mode))
ret = index_pipe(sha1, fd, type, path, flags);
- else if (size <= big_file_threshold || type != OBJ_BLOB ||
- (path && would_convert_to_git(path, NULL, 0, 0)))
- ret = index_core(sha1, fd, size, type, path, flags);
+ else if (st->st_size <= big_file_threshold || type != OBJ_BLOB ||
+ (path && would_convert_to_git(path)))
+ ret = index_core(sha1, fd, xsize_t(st->st_size), type, path,
+ flags);
else
- ret = index_stream(sha1, fd, size, type, path, flags);
+ ret = index_stream(sha1, fd, xsize_t(st->st_size), type, path,
+ flags);
close(fd);
return ret;
}
#include "cache.h"
+#include "lockfile.h"
#include "commit.h"
#include "tag.h"
#include "pkt-line.h"
if (write_shallow_commits(&sb, 0, extra)) {
if (write_in_full(fd, sb.buf, sb.len) != sb.len)
die_errno("failed to write to %s",
- shallow_lock->filename);
- *alternate_shallow_file = shallow_lock->filename;
+ shallow_lock->filename.buf);
+ *alternate_shallow_file = shallow_lock->filename.buf;
} else
/*
* is_repository_shallow() sees empty string as "no
if (write_shallow_commits_1(&sb, 0, NULL, SEEN_ONLY)) {
if (write_in_full(fd, sb.buf, sb.len) != sb.len)
die_errno("failed to write to %s",
- shallow_lock.filename);
+ shallow_lock.filename.buf);
commit_lock_file(&shallow_lock);
} else {
unlink(git_path("shallow"));
-#include "sigchain.h"
#include "cache.h"
+#include "sigchain.h"
#define SIGCHAIN_MAX_SIGNALS 32
'
}
-cat >askpass <<\EOF
-#!/bin/sh
+write_script askpass <<\EOF
echo >&2 askpass: $*
what=$(echo $1 | cut -d" " -f1 | tr A-Z a-z | tr -cd a-z)
echo "askpass-$what"
EOF
-chmod +x askpass
GIT_ASKPASS="$PWD/askpass"
export GIT_ASKPASS
PassEnv GIT_VALGRIND
PassEnv GIT_VALGRIND_OPTIONS
+PassEnv GNUPGHOME
Alias /dumb/ www/
Alias /auth/dumb/ www/auth/dumb/
:
'
-test_expect_success 'required filter success' '
- git config filter.required.smudge cat &&
- git config filter.required.clean cat &&
+test_expect_success 'required filter should filter data' '
+ git config filter.required.smudge ./rot13.sh &&
+ git config filter.required.clean ./rot13.sh &&
git config filter.required.required true &&
echo "*.r filter=required" >.gitattributes &&
- echo test >test.r &&
+ cat test.o >test.r &&
git add test.r &&
+
rm -f test.r &&
- git checkout -- test.r
+ git checkout -- test.r &&
+ cmp test.o test.r &&
+
+ ./rot13.sh <test.o >expected &&
+ git cat-file blob :test.r >actual &&
+ cmp expected actual
'
test_expect_success 'required filter smudge failure' '
test_must_fail git add test.fc
'
+test_expect_success 'filtering large input to small output should use little memory' '
+ git config filter.devnull.clean "cat >/dev/null" &&
+ git config filter.devnull.required true &&
+ for i in $(test_seq 1 30); do printf "%1048576d" 1; done >30MB &&
+ echo "30MB filter=devnull" >.gitattributes &&
+ GIT_MMAP_LIMIT=1m GIT_ALLOC_LIMIT=1m git add 30MB
+'
+
test_expect_success EXPENSIVE 'filter large file' '
git config filter.largefile.smudge cat &&
git config filter.largefile.clean cat &&
--- /dev/null
+#!/bin/sh
+
+test_description='basic tests for the SHA1 array implementation'
+. ./test-lib.sh
+
+echo20 () {
+ prefix="${1:+$1 }"
+ shift
+ while test $# -gt 0
+ do
+ echo "$prefix$1$1$1$1$1$1$1$1$1$1$1$1$1$1$1$1$1$1$1$1"
+ shift
+ done
+}
+
+test_expect_success 'ordered enumeration' '
+ echo20 "" 44 55 88 aa >expect &&
+ {
+ echo20 append 88 44 aa 55 &&
+ echo for_each_unique
+ } | test-sha1-array >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'ordered enumeration with duplicate suppression' '
+ echo20 "" 44 55 88 aa >expect &&
+ {
+ echo20 append 88 44 aa 55 &&
+ echo20 append 88 44 aa 55 &&
+ echo for_each_unique
+ } | test-sha1-array >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'lookup' '
+ {
+ echo20 append 88 44 aa 55 &&
+ echo20 lookup 55
+ } | test-sha1-array >actual &&
+ n=$(cat actual) &&
+ test "$n" -eq 1
+'
+
+test_expect_success 'lookup non-existing entry' '
+ {
+ echo20 append 88 44 aa 55 &&
+ echo20 lookup 33
+ } | test-sha1-array >actual &&
+ n=$(cat actual) &&
+ test "$n" -lt 0
+'
+
+test_expect_success 'lookup with duplicates' '
+ {
+ echo20 append 88 44 aa 55 &&
+ echo20 append 88 44 aa 55 &&
+ echo20 lookup 55
+ } | test-sha1-array >actual &&
+ n=$(cat actual) &&
+ test "$n" -ge 2 &&
+ test "$n" -le 3
+'
+
+test_expect_success 'lookup non-existing entry with duplicates' '
+ {
+ echo20 append 88 44 aa 55 &&
+ echo20 append 88 44 aa 55 &&
+ echo20 lookup 66
+ } | test-sha1-array >actual &&
+ n=$(cat actual) &&
+ test "$n" -lt 0
+'
+
+test_expect_success 'lookup with almost duplicate values' '
+ {
+ echo "append 5555555555555555555555555555555555555555" &&
+ echo "append 555555555555555555555555555555555555555f" &&
+ echo20 lookup 55
+ } | test-sha1-array >actual &&
+ n=$(cat actual) &&
+ test "$n" -eq 0
+'
+
+test_expect_success 'lookup with single duplicate value' '
+ {
+ echo20 append 55 55 &&
+ echo20 lookup 55
+ } | test-sha1-array >actual &&
+ n=$(cat actual) &&
+ test "$n" -ge 0 &&
+ test "$n" -le 1
+'
+
+test_done
# ls-files might have foo/bar, foo/bar/baz, and foo/bar/quux
# We want to count only foo because it's the only direct child
subtrees=$(git ls-files|grep /|cut -d / -f 1|uniq) &&
- subtree_count=$(echo "$subtrees"|awk '$1 {++c} END {print c}') &&
+ subtree_count=$(echo "$subtrees"|awk -v c=0 '$1 {++c} END {print c}') &&
entries=$(git ls-files|wc -l) &&
printf "SHA $dir (%d entries, %d subtrees)\n" "$entries" "$subtree_count" &&
for subtree in $subtrees
echo X | dd of=large2 bs=1k seek=2000 &&
echo X | dd of=large3 bs=1k seek=2000 &&
echo Y | dd of=huge bs=1k seek=2500 &&
- GIT_ALLOC_LIMIT=1500 &&
+ GIT_ALLOC_LIMIT=1500k &&
export GIT_ALLOC_LIMIT
'
>remote.tar.gz
'
+test_expect_success 'archive and :(glob)' '
+ git archive -v HEAD -- ":(glob)**/sh" >/dev/null 2>actual &&
+ cat >expect <<EOF &&
+a/
+a/bin/
+a/bin/sh
+EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'catch non-matching pathspec' '
+ test_must_fail git archive -v HEAD -- "*.abc" >/dev/null
+'
+
test_done
--- /dev/null
+#!/bin/sh
+
+test_description='signed push'
+
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-gpg.sh
+
+prepare_dst () {
+ rm -fr dst &&
+ test_create_repo dst &&
+
+ git push dst master:noop master:ff master:noff
+}
+
+test_expect_success setup '
+ # master, ff and noff branches pointing at the same commit
+ test_tick &&
+ git commit --allow-empty -m initial &&
+
+ git checkout -b noop &&
+ git checkout -b ff &&
+ git checkout -b noff &&
+
+ # noop stays the same, ff advances, noff rewrites
+ test_tick &&
+ git commit --allow-empty --amend -m rewritten &&
+ git checkout ff &&
+
+ test_tick &&
+ git commit --allow-empty -m second
+'
+
+test_expect_success 'unsigned push does not send push certificate' '
+ prepare_dst &&
+ mkdir -p dst/.git/hooks &&
+ write_script dst/.git/hooks/post-receive <<-\EOF &&
+ # discard the update list
+ cat >/dev/null
+ # record the push certificate
+ if test -n "${GIT_PUSH_CERT-}"
+ then
+ git cat-file blob $GIT_PUSH_CERT >../push-cert
+ fi
+ EOF
+
+ git push dst noop ff +noff &&
+ ! test -f dst/push-cert
+'
+
+test_expect_success 'talking with a receiver without push certificate support' '
+ prepare_dst &&
+ mkdir -p dst/.git/hooks &&
+ write_script dst/.git/hooks/post-receive <<-\EOF &&
+ # discard the update list
+ cat >/dev/null
+ # record the push certificate
+ if test -n "${GIT_PUSH_CERT-}"
+ then
+ git cat-file blob $GIT_PUSH_CERT >../push-cert
+ fi
+ EOF
+
+ git push dst noop ff +noff &&
+ ! test -f dst/push-cert
+'
+
+test_expect_success 'push --signed fails with a receiver without push certificate support' '
+ prepare_dst &&
+ mkdir -p dst/.git/hooks &&
+ test_must_fail git push --signed dst noop ff +noff 2>err &&
+ test_i18ngrep "the receiving end does not support" err
+'
+
+test_expect_success GPG 'no certificate for a signed push with no update' '
+ prepare_dst &&
+ mkdir -p dst/.git/hooks &&
+ write_script dst/.git/hooks/post-receive <<-\EOF &&
+ if test -n "${GIT_PUSH_CERT-}"
+ then
+ git cat-file blob $GIT_PUSH_CERT >../push-cert
+ fi
+ EOF
+ git push dst noop &&
+ ! test -f dst/push-cert
+'
+
+test_expect_success GPG 'signed push sends push certificate' '
+ prepare_dst &&
+ mkdir -p dst/.git/hooks &&
+ git -C dst config receive.certnonceseed sekrit &&
+ write_script dst/.git/hooks/post-receive <<-\EOF &&
+ # discard the update list
+ cat >/dev/null
+ # record the push certificate
+ if test -n "${GIT_PUSH_CERT-}"
+ then
+ git cat-file blob $GIT_PUSH_CERT >../push-cert
+ fi &&
+
+ cat >../push-cert-status <<E_O_F
+ SIGNER=${GIT_PUSH_CERT_SIGNER-nobody}
+ KEY=${GIT_PUSH_CERT_KEY-nokey}
+ STATUS=${GIT_PUSH_CERT_STATUS-nostatus}
+ NONCE_STATUS=${GIT_PUSH_CERT_NONCE_STATUS-nononcestatus}
+ NONCE=${GIT_PUSH_CERT_NONCE-nononce}
+ E_O_F
+
+ EOF
+
+ git push --signed dst noop ff +noff &&
+
+ (
+ cat <<-\EOF &&
+ SIGNER=C O Mitter <committer@example.com>
+ KEY=13B6F51ECDDE430D
+ STATUS=G
+ NONCE_STATUS=OK
+ EOF
+ sed -n -e "s/^nonce /NONCE=/p" -e "/^$/q" dst/push-cert
+ ) >expect &&
+
+ grep "$(git rev-parse noop ff) refs/heads/ff" dst/push-cert &&
+ grep "$(git rev-parse noop noff) refs/heads/noff" dst/push-cert &&
+ test_cmp expect dst/push-cert-status
+'
+
+test_done
fi
ROOT_PATH="$PWD"
+. "$TEST_DIRECTORY"/lib-gpg.sh
. "$TEST_DIRECTORY"/lib-httpd.sh
. "$TEST_DIRECTORY"/lib-terminal.sh
start_httpd
run_with_limited_cmdline git push --mirror
'
+test_expect_success GPG 'push with post-receive to inspect certificate' '
+ (
+ cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git &&
+ mkdir -p hooks &&
+ write_script hooks/post-receive <<-\EOF &&
+ # discard the update list
+ cat >/dev/null
+ # record the push certificate
+ if test -n "${GIT_PUSH_CERT-}"
+ then
+ git cat-file blob $GIT_PUSH_CERT >../push-cert
+ fi &&
+ cat >../push-cert-status <<E_O_F
+ SIGNER=${GIT_PUSH_CERT_SIGNER-nobody}
+ KEY=${GIT_PUSH_CERT_KEY-nokey}
+ STATUS=${GIT_PUSH_CERT_STATUS-nostatus}
+ NONCE_STATUS=${GIT_PUSH_CERT_NONCE_STATUS-nononcestatus}
+ NONCE=${GIT_PUSH_CERT_NONCE-nononce}
+ E_O_F
+ EOF
+
+ git config receive.certnonceseed sekrit &&
+ git config receive.certnonceslop 30
+ ) &&
+ cd "$ROOT_PATH/test_repo_clone" &&
+ test_commit cert-test &&
+ git push --signed "$HTTPD_URL/smart/test_repo.git" &&
+ (
+ cd "$HTTPD_DOCUMENT_ROOT_PATH" &&
+ cat <<-\EOF &&
+ SIGNER=C O Mitter <committer@example.com>
+ KEY=13B6F51ECDDE430D
+ STATUS=G
+ NONCE_STATUS=OK
+ EOF
+ sed -n -e "s/^nonce /NONCE=/p" -e "/^$/q" push-cert
+ ) >expect &&
+ test_cmp expect "$HTTPD_DOCUMENT_ROOT_PATH/push-cert-status"
+'
+
stop_httpd
test_done
'
run_with_limited_stack () {
- (ulimit -s 64 && "$@")
+ (ulimit -s 128 && "$@")
}
test_lazy_prereq ULIMIT 'run_with_limited_stack true'
test_expect_success ULIMIT '--contains works in a deep repo' '
>expect &&
i=1 &&
- while test $i -lt 4000
+ while test $i -lt 8000
do
echo "commit refs/heads/master
committer A U Thor <author@example.com> $((1000000000 + $i * 100)) +0200
}
HOME="$TRASH_DIRECTORY"
-export HOME
+GNUPGHOME="$HOME/gnupg-home-not-used"
+export HOME GNUPGHOME
if test -z "$TEST_NO_CREATE_REPO"
then
#include "tree.h"
#include "blob.h"
-#define PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----"
-#define PGP_MESSAGE "-----BEGIN PGP MESSAGE-----"
-
const char *tag_type = "tag";
struct object *deref_tag(struct object *o, const char *warn, int warnlen)
free(data);
return ret;
}
-
-/*
- * Look at a signed tag object, and return the offset where
- * the embedded detached signature begins, or the end of the
- * data when there is no such signature.
- */
-size_t parse_signature(const char *buf, unsigned long size)
-{
- char *eol;
- size_t len = 0;
- while (len < size && !starts_with(buf + len, PGP_SIGNATURE) &&
- !starts_with(buf + len, PGP_MESSAGE)) {
- eol = memchr(buf + len, '\n', size - len);
- len += eol ? eol - (buf + len) + 1 : size - len;
- }
- return len;
-}
extern int parse_tag(struct tag *item);
extern struct object *deref_tag(struct object *, const char *, int);
extern struct object *deref_tag_noverify(struct object *);
-extern size_t parse_signature(const char *buf, unsigned long size);
#endif /* TAG_H */
-#include <git-compat-util.h>
+#include "git-compat-util.h"
int main(int argc, char **argv)
{
#include "cache.h"
+#include "lockfile.h"
#include "tree.h"
#include "cache-tree.h"
--- /dev/null
+#include "cache.h"
+#include "sha1-array.h"
+
+static void print_sha1(const unsigned char sha1[20], void *data)
+{
+ puts(sha1_to_hex(sha1));
+}
+
+int main(int argc, char **argv)
+{
+ struct sha1_array array = SHA1_ARRAY_INIT;
+ struct strbuf line = STRBUF_INIT;
+
+ while (strbuf_getline(&line, stdin, '\n') != EOF) {
+ const char *arg;
+ unsigned char sha1[20];
+
+ if (skip_prefix(line.buf, "append ", &arg)) {
+ if (get_sha1_hex(arg, sha1))
+ die("not a hexadecimal SHA1: %s", arg);
+ sha1_array_append(&array, sha1);
+ } else if (skip_prefix(line.buf, "lookup ", &arg)) {
+ if (get_sha1_hex(arg, sha1))
+ die("not a hexadecimal SHA1: %s", arg);
+ printf("%d\n", sha1_array_lookup(&array, sha1));
+ } else if (!strcmp(line.buf, "clear"))
+ sha1_array_clear(&array);
+ else if (!strcmp(line.buf, "for_each_unique"))
+ sha1_array_for_each_unique(&array, print_sha1, NULL);
+ else
+ die("unknown command: %s", line.buf);
+ }
+ return 0;
+}
-#include "sigchain.h"
#include "cache.h"
+#include "sigchain.h"
#define X(f) \
static void f(int sig) { \
static const char *boolean_options[] = {
TRANS_OPT_THIN,
TRANS_OPT_KEEP,
- TRANS_OPT_FOLLOWTAGS
+ TRANS_OPT_FOLLOWTAGS,
+ TRANS_OPT_PUSH_CERT
};
static int set_helper_option(struct transport *transport,
if (flags & TRANSPORT_PUSH_DRY_RUN) {
if (set_helper_option(transport, "dry-run", "true") != 0)
die("helper %s does not support dry-run", data->name);
+ } else if (flags & TRANSPORT_PUSH_CERT) {
+ if (set_helper_option(transport, TRANS_OPT_PUSH_CERT, "true") != 0)
+ die("helper %s does not support --signed", data->name);
}
strbuf_addch(&buf, '\n');
if (flags & TRANSPORT_PUSH_DRY_RUN) {
if (set_helper_option(transport, "dry-run", "true") != 0)
die("helper %s does not support dry-run", data->name);
+ } else if (flags & TRANSPORT_PUSH_CERT) {
+ if (set_helper_option(transport, TRANS_OPT_PUSH_CERT, "true") != 0)
+ die("helper %s does not support dry-run", data->name);
}
if (flags & TRANSPORT_PUSH_FORCE) {
die("transport: invalid depth option '%s'", value);
}
return 0;
+ } else if (!strcmp(name, TRANS_OPT_PUSH_CERT)) {
+ opts->push_cert = !!value;
+ return 0;
}
return 1;
}
args.progress = transport->progress;
args.dry_run = !!(flags & TRANSPORT_PUSH_DRY_RUN);
args.porcelain = !!(flags & TRANSPORT_PUSH_PORCELAIN);
+ args.push_cert = !!(flags & TRANSPORT_PUSH_CERT);
+ args.url = transport->url;
ret = send_pack(&args, data->fd, data->conn, remote_refs,
&data->extra_have);
unsigned check_self_contained_and_connected : 1;
unsigned self_contained_and_connected : 1;
unsigned update_shallow : 1;
+ unsigned push_cert : 1;
int depth;
const char *uploadpack;
const char *receivepack;
#define TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND 256
#define TRANSPORT_PUSH_NO_HOOK 512
#define TRANSPORT_PUSH_FOLLOW_TAGS 1024
+#define TRANSPORT_PUSH_CERT 2048
#define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
#define TRANSPORT_SUMMARY(x) (int)(TRANSPORT_SUMMARY_WIDTH + strlen(x) - gettext_width(x)), (x)
/* Accept refs that may update .git/shallow without --depth */
#define TRANS_OPT_UPDATE_SHALLOW "updateshallow"
+/* Send push certificates */
+#define TRANS_OPT_PUSH_CERT "pushcert"
+
/**
* Returns 0 if the option was used, non-zero otherwise. Prints a
* message to stderr if the option is not used.
+#include "git-compat-util.h"
#include "varint.h"
uintmax_t decode_varint(const unsigned char **bufp)
#ifndef VARINT_H
#define VARINT_H
-#include "git-compat-util.h"
-
extern int encode_varint(uintmax_t, unsigned char *);
extern uintmax_t decode_varint(const unsigned char **);
static int memory_limit_check(size_t size, int gentle)
{
- static int limit = -1;
- if (limit == -1) {
- const char *env = getenv("GIT_ALLOC_LIMIT");
- limit = env ? atoi(env) * 1024 : 0;
+ static size_t limit = 0;
+ if (!limit) {
+ limit = git_env_ulong("GIT_ALLOC_LIMIT", 0);
+ if (!limit)
+ limit = SIZE_MAX;
}
- if (limit && size > limit) {
+ if (size > limit) {
if (gentle) {
- error("attempting to allocate %"PRIuMAX" over limit %d",
- (intmax_t)size, limit);
+ error("attempting to allocate %"PRIuMAX" over limit %"PRIuMAX,
+ (uintmax_t)size, (uintmax_t)limit);
return -1;
} else
- die("attempting to allocate %"PRIuMAX" over limit %d",
- (intmax_t)size, limit);
+ die("attempting to allocate %"PRIuMAX" over limit %"PRIuMAX,
+ (uintmax_t)size, (uintmax_t)limit);
}
return 0;
}