Update only files that git already knows about. This is similar
to what "git commit -a" does in preparation for making a commit,
except that the update is limited to paths specified on the
- command line. If no paths are specified, all tracked files are
- updated.
+ command line. If no paths are specified, all tracked files in the
+ current directory and its subdirectories are updated.
\--refresh::
Don't add the file(s), but only refresh their stat()
-----------
Runs `git-fetch` with the given parameters, and calls `git-merge`
to merge the retrieved head(s) into the current branch.
+With `--rebase`, calls `git-rebase` instead of `git-merge`.
Note that you can use `.` (current directory) as the
<repository> to pull from the local repository -- this is useful
include::merge-options.txt[]
:git-pull: 1
-include::fetch-options.txt[]
-
-include::pull-fetch-param.txt[]
-
-include::urls-remotes.txt[]
-
-include::merge-strategies.txt[]
\--rebase::
Instead of a merge, perform a rebase after fetching. If
there is a remote ref for the upstream branch, and this branch
was rebased since last fetched, the rebase uses that information
- to avoid rebasing non-local changes.
+ to avoid rebasing non-local changes. To make this the default
+ for branch `<name>`, set configuration `branch.<name>.rebase`
+ to `true`.
+
*NOTE:* This is a potentially _dangerous_ mode of operation.
It rewrites history, which does not bode well when you
\--no-rebase::
Override earlier \--rebase.
+include::fetch-options.txt[]
+
+include::pull-fetch-param.txt[]
+
+include::urls-remotes.txt[]
+
+include::merge-strategies.txt[]
+
DEFAULT BEHAVIOUR
-----------------
+
Note: If no explicit refspec is found, (that is neither
on the command line nor in any Push line of the
-corresponding remotes file---see below), then all the
-heads that exist both on the local side and on the remote
-side are updated.
+corresponding remotes file---see below), then "matching" heads are
+pushed: for every head that exists on the local side, the remote side is
+updated if a head of the same name already exists on the remote side.
+
`tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`.
+
include::urls-remotes.txt[]
+OUTPUT
+------
+
+The output of "git push" depends on the transport method used; this
+section describes the output when pushing over the git protocol (either
+locally or via ssh).
+
+The status of the push is output in tabular form, with each line
+representing the status of a single ref. Each line is of the form:
+
+-------------------------------
+ <flag> <summary> <from> -> <to> (<reason>)
+-------------------------------
+
+flag::
+ A single character indicating the status of the ref. This is
+ blank for a successfully pushed ref, `!` for a ref that was
+ rejected or failed to push, and '=' for a ref that was up to
+ date and did not need pushing (note that the status of up to
+ date refs is shown only when `git push` is running verbosely).
+
+summary::
+ For a successfully pushed ref, the summary shows the old and new
+ values of the ref in a form suitable for using as an argument to
+ `git log` (this is `<old>..<new>` in most cases, and
+ `<old>...<new>` for forced non-fast forward updates). For a
+ failed update, more details are given for the failure.
+ The string `rejected` indicates that git did not try to send the
+ ref at all (typically because it is not a fast forward). The
+ string `remote rejected` indicates that the remote end refused
+ the update; this rejection is typically caused by a hook on the
+ remote side. The string `remote failure` indicates that the
+ remote end did not report the successful update of the ref
+ (perhaps because of a temporary error on the remote side, a
+ break in the network connection, or other transient error).
+
+from::
+ The name of the local ref being pushed, minus its
+ `refs/<type>/` prefix. In the case of deletion, the
+ name of the local ref is omitted.
+
+to::
+ The name of the remote ref being updated, minus its
+ `refs/<type>/` prefix.
+
+reason::
+ A human-readable explanation. In the case of successfully pushed
+ refs, no explanation is needed. For a failed ref, the reason for
+ failure is described.
Examples
--------
subcommand is given. The <message> part is optional and gives
the description along with the stashed state.
-list::
+list [<options>]::
List the stashes that you currently have. Each 'stash' is listed
with its name (e.g. `stash@\{0}` is the latest stash, `stash@\{1}` is
stash@{0}: WIP on submit: 6ebd0e2... Update git-stash documentation
stash@{1}: On master: 9cc0589... Add git-stash
----------------------------------------------------------------
++
+The command takes options applicable to the linkgit:git-log[1]
+command to control what is shown and how.
show [<stash>]::
--- /dev/null
+Remotes configuration API
+=========================
+
+The API in remote.h gives access to the configuration related to
+remotes. It handles all three configuration mechanisms historically
+and currently used by git, and presents the information in a uniform
+fashion. Note that the code also handles plain URLs without any
+configuration, giving them just the default information.
+
+struct remote
+-------------
+
+`name`::
+
+ The user's nickname for the remote
+
+`url`::
+
+ An array of all of the url_nr URLs configured for the remote
+
+`push`::
+
+ An array of refspecs configured for pushing, with
+ push_refspec being the literal strings, and push_refspec_nr
+ being the quantity.
+
+`fetch`::
+
+ An array of refspecs configured for fetching, with
+ fetch_refspec being the literal strings, and fetch_refspec_nr
+ being the quantity.
+
+`fetch_tags`::
+
+ The setting for whether to fetch tags (as a separate rule from
+ the configured refspecs); -1 means never to fetch tags, 0
+ means to auto-follow tags based on the default heuristic, 1
+ means to always auto-follow tags, and 2 means to fetch all
+ tags.
+
+`receivepack`, `uploadpack`::
+
+ The configured helper programs to run on the remote side, for
+ git-native protocols.
+
+`http_proxy`::
+
+ The proxy to use for curl (http, https, ftp, etc.) URLs.
+
+struct remotes can be found by name with remote_get(), and iterated
+through with for_each_remote(). remote_get(NULL) will return the
+default remote, given the current branch and configuration.
+
+struct refspec
+--------------
+
+A struct refspec holds the parsed interpretation of a refspec. If it
+will force updates (starts with a '+'), force is true. If it is a
+pattern (sides end with '*') pattern is true. src and dest are the two
+sides (if a pattern, only the part outside of the wildcards); if there
+is only one side, it is src, and dst is NULL; if sides exist but are
+empty (i.e., the refspec either starts or ends with ':'), the
+corresponding side is "".
+
+This parsing can be done to an array of strings to give an array of
+struct refpsecs with parse_ref_spec().
+
+remote_find_tracking(), given a remote and a struct refspec with
+either src or dst filled out, will fill out the other such that the
+result is in the "fetch" specification for the remote (note that this
+evaluates patterns and returns a single result).
+
+struct branch
+-------------
+
+Note that this may end up moving to branch.h
+
+struct branch holds the configuration for a branch. It can be looked
+up with branch_get(name) for "refs/heads/{name}", or with
+branch_get(NULL) for HEAD.
+
+It contains:
+
+`name`::
+
+ The short name of the branch.
+
+`refname`::
+
+ The full path for the branch ref.
+
+`remote_name`::
+
+ The name of the remote listed in the configuration.
+
+`remote`::
+
+ The struct remote for that remote.
+
+`merge_name`::
+
+ An array of the "merge" lines in the configuration.
+
+`merge`::
+
+ An array of the struct refspecs used for the merge lines. That
+ is, merge[i]->dst is a local tracking ref which should be
+ merged into this branch by default.
+
+`merge_nr`::
+
+ The number of merge configurations
+
+branch_has_merge_config() returns true if the given branch has merge
+configuration given.
+
+Other stuff
+-----------
+
+There is other stuff in remote.h that is related, in general, to the
+process of interacting with remotes.
+
+(Daniel Barkalow)
run-command API
===============
-Talk about <run-command.h>, and things like:
+The run-command API offers a versatile tool to run sub-processes with
+redirected input and output as well as with a modified environment
+and an alternate current directory.
-* Environment the command runs with (e.g. GIT_DIR);
-* File descriptors and pipes;
-* Exit status;
+A similar API offers the capability to run a function asynchronously,
+which is primarily used to capture the output that the function
+produces in the caller in order to process it.
-(Hannes, Dscho, Shawn)
+
+Functions
+---------
+
+`start_command`::
+
+ Start a sub-process. Takes a pointer to a `struct child_process`
+ that specifies the details and returns pipe FDs (if requested).
+ See below for details.
+
+`finish_command`::
+
+ Wait for the completion of a sub-process that was started with
+ start_command().
+
+`run_command`::
+
+ A convenience function that encapsulates a sequence of
+ start_command() followed by finish_command(). Takes a pointer
+ to a `struct child_process` that specifies the details.
+
+`run_command_v_opt`, `run_command_v_opt_dir`, `run_command_v_opt_cd_env`::
+
+ Convenience functions that encapsulate a sequence of
+ start_command() followed by finish_command(). The argument argv
+ specifies the program and its arguments. The argument opt is zero
+ or more of the flags `RUN_COMMAND_NO_STDIN`, `RUN_GIT_CMD`, or
+ `RUN_COMMAND_STDOUT_TO_STDERR` that correspond to the members
+ .no_stdin, .git_cmd, .stdout_to_stderr of `struct child_process`.
+ The argument dir corresponds the member .dir. The argument env
+ corresponds to the member .env.
+
+`start_async`::
+
+ Run a function asynchronously. Takes a pointer to a `struct
+ async` that specifies the details and returns a pipe FD
+ from which the caller reads. See below for details.
+
+`finish_async`::
+
+ Wait for the completeion of an asynchronous function that was
+ started with start_async().
+
+
+Data structures
+---------------
+
+* `struct child_process`
+
+This describes the arguments, redirections, and environment of a
+command to run in a sub-process.
+
+The caller:
+
+1. allocates and clears (memset(&chld, '0', sizeof(chld));) a
+ struct child_process variable;
+2. initializes the members;
+3. calls start_command();
+4. processes the data;
+5. closes file descriptors (if necessary; see below);
+6. calls finish_command().
+
+The .argv member is set up as an array of string pointers (NULL
+terminated), of which .argv[0] is the program name to run (usually
+without a path). If the command to run is a git command, set argv[0] to
+the command name without the 'git-' prefix and set .git_cmd = 1.
+
+The members .in, .out, .err are used to redirect stdin, stdout,
+stderr as follows:
+
+. Specify 0 to request no special redirection. No new file descriptor
+ is allocated. The child process simply inherits the channel from the
+ parent.
+
+. Specify -1 to have a pipe allocated; start_command() replaces -1
+ by the pipe FD in the following way:
+
+ .in: Returns the writable pipe end into which the caller writes;
+ the readable end of the pipe becomes the child's stdin.
+
+ .out, .err: Returns the readable pipe end from which the caller
+ reads; the writable end of the pipe end becomes child's
+ stdout/stderr.
+
+ The caller of start_command() must close the so returned FDs
+ after it has completed reading from/writing to it!
+
+. Specify a file descriptor > 0 to be used by the child:
+
+ .in: The FD must be readable; it becomes child's stdin.
+ .out: The FD must be writable; it becomes child's stdout.
+ .err > 0 is not supported.
+
+ The specified FD is closed by start_command(), even if it fails to
+ run the sub-process!
+
+. Special forms of redirection are available by setting these members
+ to 1:
+
+ .no_stdin, .no_stdout, .no_stderr: The respective channel is
+ redirected to /dev/null.
+
+ .stdout_to_stderr: stdout of the child is redirected to the
+ parent's stderr (i.e. *not* to what .err or
+ .no_stderr specify).
+
+To modify the environment of the sub-process, specify an array of
+string pointers (NULL terminated) in .env:
+
+. If the string is of the form "VAR=value", i.e. it contains '='
+ the variable is added to the child process's environment.
+
+. If the string does not contain '=', it names an environement
+ variable that will be removed from the child process's envionment.
+
+To specify a new initial working directory for the sub-process,
+specify it in the .dir member.
+
+
+* `struct async`
+
+This describes a function to run asynchronously, whose purpose is
+to produce output that the caller reads.
+
+The caller:
+
+1. allocates and clears (memset(&asy, '0', sizeof(asy));) a
+ struct async variable;
+2. initializes .proc and .data;
+3. calls start_async();
+4. processes the data by reading from the fd in .out;
+5. closes .out;
+6. calls finish_async().
+
+The function pointer in .proc has the following signature:
+
+ int proc(int fd, void *data);
+
+. fd specifies a writable file descriptor to which the function must
+ write the data that it produces. The function *must* close this
+ descriptor before it returns.
+
+. data is the value that the caller has specified in the .data member
+ of struct async.
+
+. The return value of the function is 0 on success and non-zero
+ on failure. If the function indicates failure, finish_async() will
+ report failure as well.
+
+
+There are serious restrictions on what the asynchronous function can do
+because this facility is implemented by a pipe to a forked process on
+UNIX, but by a thread in the same address space on Windows:
+
+. It cannot change the program's state (global variables, environment,
+ etc.) in a way that the caller notices; in other words, .out is the
+ only communication channel to the caller.
+
+. It must not change the program's state that the caller of the
+ facility also uses.
# Define V=1 to have a more verbose compile.
#
+# Define FREAD_READS_DIRECTORIES if your are on a system which succeeds
+# when attempting to read from an fopen'ed directory.
+#
# Define NO_OPENSSL environment variable if you do not have OpenSSL.
# This also implies MOZILLA_SHA1.
#
ifdef NO_C99_FORMAT
BASIC_CFLAGS += -DNO_C99_FORMAT
endif
+ifdef FREAD_READS_DIRECTORIES
+ COMPAT_CFLAGS += -DFREAD_READS_DIRECTORIES
+ COMPAT_OBJS += compat/fopen.o
+endif
ifdef NO_SYMLINK_HEAD
BASIC_CFLAGS += -DNO_SYMLINK_HEAD
endif
goto finish;
}
+ if (*argv) {
+ /* Was there an invalid path? */
+ if (pathspec) {
+ int num;
+ for (num = 0; pathspec[num]; num++)
+ ; /* just counting */
+ if (argc != num)
+ exit(1); /* error message already given */
+ } else
+ exit(1); /* error message already given */
+ }
+
fill_directory(&dir, pathspec, ignored_too);
if (show_only) {
static const char *add_prefix(const char *prefix, const char *path)
{
- if (!prefix || !prefix[0])
- return path;
- return prefix_path(prefix, strlen(prefix), path);
+ return prefix_path(prefix, prefix ? strlen(prefix) : 0, path);
}
/*
* bottom commits we would reach while traversing as
* uninteresting.
*/
- prepare_revision_walk(&revs);
+ if (prepare_revision_walk(&revs))
+ die("revision walk setup failed");
if (is_null_sha1(sb.final->object.sha1)) {
char *buf;
get_tags_and_duplicates(&revs.pending, &extra_refs);
- prepare_revision_walk(&revs);
+ if (prepare_revision_walk(&revs))
+ die("revision walk setup failed");
revs.diffopt.format_callback = show_filemodify;
DIFF_OPT_SET(&revs.diffopt, RECURSIVE);
while ((commit = get_revision(&revs))) {
add_pending_object(rev, branch, name);
add_pending_object(rev, &head->object, "^HEAD");
head->object.flags |= UNINTERESTING;
- prepare_revision_walk(rev);
+ if (prepare_revision_walk(rev))
+ die("revision walk setup failed");
while ((commit = get_revision(rev)) != NULL) {
char *oneline, *bol, *eol;
if (rev->early_output)
setup_early_output(rev);
- prepare_revision_walk(rev);
+ if (prepare_revision_walk(rev))
+ die("revision walk setup failed");
if (rev->early_output)
finish_early_output(rev);
o2->flags ^= UNINTERESTING;
add_pending_object(&check_rev, o1, "o1");
add_pending_object(&check_rev, o2, "o2");
- prepare_revision_walk(&check_rev);
+ if (prepare_revision_walk(&check_rev))
+ die("revision walk setup failed");
while ((commit = get_revision(&check_rev)) != NULL) {
/* ignore merges */
if (!use_stdout)
realstdout = xfdopen(xdup(1), "w");
- prepare_revision_walk(&rev);
+ if (prepare_revision_walk(&rev))
+ die("revision walk setup failed");
while ((commit = get_revision(&rev)) != NULL) {
/* ignore merges */
if (commit->parents && commit->parents->next)
die("Unknown commit %s", limit);
/* reverse the list of commits */
- prepare_revision_walk(&revs);
+ if (prepare_revision_walk(&revs))
+ die("revision walk setup failed");
while ((commit = get_revision(&revs)) != NULL) {
/* ignore merges */
if (commit->parents && commit->parents->next)
pathspec = get_pathspec(prefix, argv + i);
/* Verify that the pathspec matches the prefix */
- if (pathspec)
+ if (pathspec) {
+ if (argc != i) {
+ int cnt;
+ for (cnt = 0; pathspec[cnt]; cnt++)
+ ;
+ if (cnt != (argc - i))
+ exit(1); /* error message already given */
+ }
prefix = verify_pathspec(prefix);
+ } else if (argc != i)
+ exit(1); /* error message already given */
/* Treat unmatching pathspec elements as errors */
if (pathspec && error_unmatch) {
int count, int base_name)
{
int i;
+ int len = prefix ? strlen(prefix) : 0;
const char **result = xmalloc((count + 1) * sizeof(const char *));
memcpy(result, pathspec, count * sizeof(const char *));
result[count] = NULL;
if (last_slash)
result[i] = last_slash + 1;
}
+ result[i] = prefix_path(prefix, len, result[i]);
+ if (!result[i])
+ exit(1); /* error already given */
}
- return get_pathspec(prefix, result);
+ return result;
}
static void show_list(const char *label, struct path_list *list)
}
dst = add_slash(dst);
- dst_len = strlen(dst) - 1;
+ dst_len = strlen(dst);
for (j = 0; j < last - first; j++) {
const char *path =
source[argc + j] = path;
destination[argc + j] =
prefix_path(dst, dst_len,
- path + length);
+ path + length + 1);
modes[argc + j] = INDEX;
}
argc += last - first;
die("bad revision '%s'", line);
}
- prepare_revision_walk(&revs);
+ if (prepare_revision_walk(&revs))
+ die("revision walk setup failed");
mark_edges_uninteresting(revs.commits, &revs, show_edge);
traverse_commit_list(&revs, show_commit, show_object);
if (!err)
continue;
- error("failed to push to '%s'", remote->url[i]);
+ error("failed to push some refs to '%s'", remote->url[i]);
errs++;
}
return !!errs;
fputs(header_prefix, stdout);
if (commit->object.flags & BOUNDARY)
putchar('-');
+ else if (commit->object.flags & UNINTERESTING)
+ putchar('^');
else if (revs.left_right) {
if (commit->object.flags & SYMMETRIC_LEFT)
putchar('<');
else
putchar('\n');
- if (revs.verbose_header) {
+ if (revs.verbose_header && commit->buffer) {
struct strbuf buf;
strbuf_init(&buf, 0);
pretty_print_commit(revs.commit_format, commit,
if (bisect_list)
revs.limited = 1;
- prepare_revision_walk(&revs);
+ if (prepare_revision_walk(&revs))
+ die("revision walk setup failed");
if (revs.tree_objects)
mark_edges_uninteresting(revs.commits, &revs, show_edge);
{
struct commit *commit;
- prepare_revision_walk(rev);
+ if (prepare_revision_walk(rev))
+ die("revision walk setup failed");
while ((commit = get_revision(rev)) != NULL) {
const char *author = NULL, *buffer;
sha1_to_hex(sha1));
if (obj->type == OBJ_TAG) {
obj = deref_tag(obj, refname, 0);
+ if (!obj)
+ die("git-show-ref: bad tag at ref %s (%s)", refname,
+ sha1_to_hex(sha1));
hex = find_unique_abbrev(obj->sha1, abbrev);
printf("%s %s^{}\n", hex, refname);
}
add_object_array(e->item, e->name, &refs);
}
- prepare_revision_walk(&revs);
+ if (prepare_revision_walk(&revs))
+ die("revision walk setup failed");
i = req_nr;
while (i && (commit = get_revision(&revs)))
unsigned long size;
int ret;
+ if (!item)
+ return -1;
if (item->object.parsed)
return 0;
buffer = read_sha1_file(item->object.sha1, &type, &size);
while (parents) {
struct commit *commit = parents->item;
- parse_commit(commit);
- if (!(commit->object.flags & mark)) {
+ if (!parse_commit(commit) && !(commit->object.flags & mark)) {
commit->object.flags |= mark;
insert_by_date(commit, list);
}
*/
return commit_list_insert(one, &result);
- parse_commit(one);
- parse_commit(two);
+ if (parse_commit(one))
+ return NULL;
+ if (parse_commit(two))
+ return NULL;
one->object.flags |= PARENT1;
two->object.flags |= PARENT2;
parents = parents->next;
if ((p->object.flags & flags) == flags)
continue;
- parse_commit(p);
+ if (parse_commit(p))
+ return NULL;
p->object.flags |= flags;
insert_by_date(p, &list);
}
--- /dev/null
+#include "../git-compat-util.h"
+#undef fopen
+FILE *git_fopen(const char *path, const char *mode)
+{
+ FILE *fp;
+ struct stat st;
+
+ if (mode[0] == 'w' || mode[0] == 'a')
+ return fopen(path, mode);
+
+ if (!(fp = fopen(path, mode)))
+ return NULL;
+
+ if (fstat(fileno(fp), &st)) {
+ fclose(fp);
+ return NULL;
+ }
+
+ if (S_ISDIR(st.st_mode)) {
+ fclose(fp);
+ errno = EISDIR;
+ return NULL;
+ }
+
+ return fp;
+}
(defun git-call-process-env (buffer env &rest args)
"Wrapper for call-process that sets environment strings."
- (if env
- (apply #'call-process "env" nil buffer nil
- (append (git-get-env-strings env) (list "git") args))
+ (let ((process-environment (append (git-get-env-strings env)
+ process-environment)))
(apply #'call-process "git" nil buffer nil args)))
(defun git-call-process-display-error (&rest args)
(defun git-call-process-env-string (env &rest args)
"Wrapper for call-process that sets environment strings,
-and returns the process output as a string."
+and returns the process output as a string, or nil if the git failed."
(with-temp-buffer
(and (eq 0 (apply #' git-call-process-env t env args))
(buffer-string))))
my $status_fmt = '%12s %12s %s';
my $status_head = sprintf($status_fmt, 'staged', 'unstaged', 'path');
+{
+ my $initial;
+ sub is_initial_commit {
+ $initial = system('git rev-parse HEAD -- >/dev/null 2>&1') != 0
+ unless defined $initial;
+ return $initial;
+ }
+}
+
+sub get_empty_tree {
+ return '4b825dc642cb6eb9a060e54bf8d69288fbee4904';
+}
+
# Returns list of hashes, contents of each of which are:
# VALUE: pathname
# BINARY: is a binary path
return if (!@tracked);
}
+ my $reference = is_initial_commit() ? get_empty_tree() : 'HEAD';
for (run_cmd_pipe(qw(git diff-index --cached
- --numstat --summary HEAD --), @tracked)) {
+ --numstat --summary), $reference,
+ '--', @tracked)) {
if (($add, $del, $file) =
/^([-\d]+) ([-\d]+) (.*)/) {
my ($change, $bin);
HEADER => $status_head, },
list_modified());
if (@update) {
- my @lines = run_cmd_pipe(qw(git ls-tree HEAD --),
- map { $_->{VALUE} } @update);
- my $fh;
- open $fh, '| git update-index --index-info'
- or die;
- for (@lines) {
- print $fh $_;
+ if (is_initial_commit()) {
+ system(qw(git rm --cached),
+ map { $_->{VALUE} } @update);
}
- close($fh);
- for (@update) {
- if ($_->{INDEX_ADDDEL} &&
- $_->{INDEX_ADDDEL} eq 'create') {
- system(qw(git update-index --force-remove --),
- $_->{VALUE});
- print "note: $_->{VALUE} is untracked now.\n";
+ else {
+ my @lines = run_cmd_pipe(qw(git ls-tree HEAD --),
+ map { $_->{VALUE} } @update);
+ my $fh;
+ open $fh, '| git update-index --index-info'
+ or die;
+ for (@lines) {
+ print $fh $_;
+ }
+ close($fh);
+ for (@update) {
+ if ($_->{INDEX_ADDDEL} &&
+ $_->{INDEX_ADDDEL} eq 'create') {
+ system(qw(git update-index --force-remove --),
+ $_->{VALUE});
+ print "note: $_->{VALUE} is untracked now.\n";
+ }
}
}
refresh();
HEADER => $status_head, },
@mods);
return if (!@them);
- system(qw(git diff -p --cached HEAD --), map { $_->{VALUE} } @them);
+ my $reference = is_initial_commit() ? get_empty_tree() : 'HEAD';
+ system(qw(git diff -p --cached), $reference, '--',
+ map { $_->{VALUE} } @them);
}
sub quit_cmd {
if test $# = 0
then
- case "${DISPLAY+set}" in
+ case "${DISPLAY+set}${MSYSTEM+set}${SECURITYSESSIONID+set}" in
'') set git log ;;
- set) set gitk ;;
+ set*) set gitk ;;
esac
else
case "$1" in
cd "$D" || exit
fi
-if test -z "$bare" && test -f "$GIT_DIR/REMOTE_HEAD"
+if test -z "$bare"
then
# a non-bare repository is always in separate-remote layout
remote_top="refs/remotes/$origin"
- head_sha1=`cat "$GIT_DIR/REMOTE_HEAD"`
+ head_sha1=
+ test ! -r "$GIT_DIR/REMOTE_HEAD" || head_sha1=`cat "$GIT_DIR/REMOTE_HEAD"`
case "$head_sha1" in
'ref: refs/'*)
# Uh-oh, the remote told us (http transport done against
git config branch."$head_points_at".merge "refs/heads/$head_points_at"
;;
'')
- # Source had detached HEAD pointing nowhere
- git update-ref --no-deref HEAD "$head_sha1" &&
- rm -f "refs/remotes/$origin/HEAD"
+ if test -z "$head_sha1"
+ then
+ # Source had nonexistent ref in HEAD
+ echo >&2 "Warning: Remote HEAD refers to nonexistent ref, unable to checkout."
+ no_checkout=t
+ else
+ # Source had detached HEAD pointing nowhere
+ git update-ref --no-deref HEAD "$head_sha1" &&
+ rm -f "refs/remotes/$origin/HEAD"
+ fi
;;
esac
const void *needle, size_t needlelen);
#endif
+#ifdef FREAD_READS_DIRECTORIES
+#define fopen(a,b) git_fopen(a,b)
+extern FILE *git_fopen(const char*, const char*);
+#endif
+
#ifdef __GLIBC_PREREQ
#if __GLIBC_PREREQ(2, 1)
#define HAVE_STRCHRNUL
my @updated = xargs_safe_pipe_capture([@cvs, 'update'], @canstatusfiles);
print @updated;
}
- my @cvsoutput;
- @cvsoutput = xargs_safe_pipe_capture([@cvs, 'status'], @canstatusfiles);
- my $matchcount = 0;
- foreach my $l (@cvsoutput) {
- chomp $l;
- if ( $l =~ /^File:/ and $l =~ /Status: (.*)$/ ) {
- $cvsstat{$canstatusfiles[$matchcount]} = $1;
- $matchcount++;
+ # "cvs status" reorders the parameters, notably when there are multiple
+ # arguments with the same basename. So be precise here.
+
+ my %added = map { $_ => 1 } @afiles;
+ my %todo = map { $_ => 1 } @canstatusfiles;
+
+ while (%todo) {
+ my @canstatusfiles2 = ();
+ my %fullname = ();
+ foreach my $name (keys %todo) {
+ my $basename = basename($name);
+
+ $basename = "no file " . $basename if (exists($added{$basename}));
+ chomp($basename);
+
+ if (!exists($fullname{$basename})) {
+ $fullname{$basename} = $name;
+ push (@canstatusfiles2, $name);
+ delete($todo{$name});
}
+ }
+ my @cvsoutput;
+ @cvsoutput = xargs_safe_pipe_capture([@cvs, 'status'], @canstatusfiles2);
+ foreach my $l (@cvsoutput) {
+ chomp $l;
+ if ($l =~ /^File:\s+(.*\S)\s+Status: (.*)$/) {
+ if (!exists($fullname{$1})) {
+ print STDERR "Huh? Status reported for unexpected file '$1'\n";
+ } else {
+ $cvsstat{$fullname{$1}} = $2;
+ }
+ }
+ }
}
}
$initial_reply_to = $_;
}
-if (defined $initial_reply_to && $_ ne "") {
+if (defined $initial_reply_to) {
$initial_reply_to =~ s/^\s*<?/</;
$initial_reply_to =~ s/>?\s*$/>/;
}
# if we require to be in a git repository.
if test -z "$NONGIT_OK"
then
+ GIT_DIR=$(git rev-parse --git-dir) || exit
if [ -z "$SUBDIRECTORY_OK" ]
then
- : ${GIT_DIR=.git}
test -z "$(git rev-parse --show-cdup)" || {
exit=$?
echo >&2 "You need to run this command from the toplevel of the working tree."
exit $exit
}
- else
- GIT_DIR=$(git rev-parse --git-dir) || {
- exit=$?
- echo >&2 "Failed to find a valid git directory."
- exit $exit
- }
fi
test -n "$GIT_DIR" && GIT_DIR=$(cd "$GIT_DIR" && pwd) || {
echo >&2 "Unable to determine absolute path of git directory"
Name: git
Version: @@VERSION@@
Release: 1%{?dist}
-Summary: Git core and tools
+Summary: Core git tools
License: GPL
Group: Development/Tools
URL: http://kernel.org/pub/software/scm/git/
BuildRequires: zlib-devel >= 1.2, openssl-devel, curl-devel, expat-devel, gettext %{!?_without_docs:, xmlto, asciidoc > 6.0.3}
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
-Requires: git-core = %{version}-%{release}
-Requires: git-svn = %{version}-%{release}
-Requires: git-cvs = %{version}-%{release}
-Requires: git-arch = %{version}-%{release}
-Requires: git-email = %{version}-%{release}
-Requires: gitk = %{version}-%{release}
-Requires: git-gui = %{version}-%{release}
Requires: perl-Git = %{version}-%{release}
+Requires: zlib >= 1.2, rsync, curl, less, openssh-clients, expat
+Provides: git-core = %{version}-%{release}
+Obsoletes: git-core <= 1.5.4.2
+Obsoletes: git-p4
%description
Git is a fast, scalable, distributed revision control system with an
unusually rich command set that provides both high-level operations
and full access to internals.
-This is a dummy package which brings in all subpackages.
+The git rpm installs the core tools with minimal dependencies. To
+install all git packages, including tools for integrating with other
+SCMs, install the git-all meta-package.
-%package core
-Summary: Core git tools
+%package all
+Summary: Meta-package to pull in all git tools
Group: Development/Tools
-Requires: zlib >= 1.2, rsync, curl, less, openssh-clients, expat
-Obsoletes: git-p4
-%description core
+Requires: git = %{version}-%{release}
+Requires: git-svn = %{version}-%{release}
+Requires: git-cvs = %{version}-%{release}
+Requires: git-arch = %{version}-%{release}
+Requires: git-email = %{version}-%{release}
+Requires: gitk = %{version}-%{release}
+Requires: git-gui = %{version}-%{release}
+Obsoletes: git <= 1.5.4.2
+
+%description all
Git is a fast, scalable, distributed revision control system with an
unusually rich command set that provides both high-level operations
and full access to internals.
-These are the core tools with minimal dependencies.
+This is a dummy package which brings in all subpackages.
%package svn
Summary: Git tools for importing Subversion repositories
Group: Development/Tools
-Requires: git-core = %{version}-%{release}, subversion
+Requires: git = %{version}-%{release}, subversion
%description svn
Git tools for importing Subversion repositories.
%package cvs
Summary: Git tools for importing CVS repositories
Group: Development/Tools
-Requires: git-core = %{version}-%{release}, cvs, cvsps
+Requires: git = %{version}-%{release}, cvs, cvsps
%description cvs
Git tools for importing CVS repositories.
%package arch
Summary: Git tools for importing Arch repositories
Group: Development/Tools
-Requires: git-core = %{version}-%{release}, tla
+Requires: git = %{version}-%{release}, tla
%description arch
Git tools for importing Arch repositories.
%package email
Summary: Git tools for sending email
Group: Development/Tools
-Requires: git-core = %{version}-%{release}
+Requires: git = %{version}-%{release}
%description email
Git tools for sending email.
%package gui
Summary: Git GUI tool
Group: Development/Tools
-Requires: git-core = %{version}-%{release}, tk >= 8.4
+Requires: git = %{version}-%{release}, tk >= 8.4
%description gui
Git GUI tool
%package -n gitk
Summary: Git revision tree visualiser ('gitk')
Group: Development/Tools
-Requires: git-core = %{version}-%{release}, tk >= 8.4
+Requires: git = %{version}-%{release}, tk >= 8.4
%description -n gitk
Git revision tree visualiser ('gitk')
%package -n perl-Git
Summary: Perl interface to Git
Group: Development/Libraries
-Requires: git-core = %{version}-%{release}
+Requires: git = %{version}-%{release}
Requires: perl(:MODULE_COMPAT_%(eval "`%{__perl} -V:version`"; echo $version))
BuildRequires: perl(Error)
%clean
rm -rf $RPM_BUILD_ROOT
-%files
-# These are no files in the root package
+%files -f bin-man-doc-files
+%defattr(-,root,root)
+%{_datadir}/git-core/
+%doc README COPYING Documentation/*.txt
+%{!?_without_docs: %doc Documentation/*.html Documentation/howto}
+%{!?_without_docs: %doc Documentation/technical}
%files svn
%defattr(-,root,root)
%files -n perl-Git -f perl-files
%defattr(-,root,root)
-%files core -f bin-man-doc-files
-%defattr(-,root,root)
-%{_datadir}/git-core/
-%doc README COPYING Documentation/*.txt
-%{!?_without_docs: %doc Documentation/*.html Documentation/howto}
-%{!?_without_docs: %doc Documentation/technical}
+%files all
+# No files for you!
%changelog
+* Fri Feb 15 2008 Kristian Høgsberg <krh@redhat.com>
+- Rename git-core to just git and rename meta package from git to git-all.
+
* Sun Feb 03 2008 James Bowes <jbowes@dangerouslyinc.com>
- Add a BuildRequires for gettext
Displayed in the project summary page. You can use multiple-valued
gitweb.url repository configuration variable for that, but the file
takes precendence.
+ * gitweb.owner
+ You can use the gitweb.owner repository configuration variable to set
+ repository's owner. It is displayed in the project list and summary
+ page. If it's not set, filesystem directory's owner is used.
* various gitweb.* config variables (in config)
Read description of %feature hash for detailed list, and some
descriptions.
);
my %mapping = @mapping;
+ $params{'project'} = $project unless exists $params{'project'};
+
if ($params{-replay}) {
while (my ($name, $symbol) = each %mapping) {
if (!exists $params{$name}) {
}
}
- $params{'project'} = $project unless exists $params{'project'};
-
my ($use_pathinfo) = gitweb_check_feature('pathinfo');
if ($use_pathinfo) {
# use PATH_INFO for project name
# Make control characters "printable", using character escape codes (CEC)
sub quot_cec {
my $cntrl = shift;
+ my %opts = @_;
my %es = ( # character escape codes, aka escape sequences
- "\t" => '\t', # tab (HT)
- "\n" => '\n', # line feed (LF)
- "\r" => '\r', # carrige return (CR)
- "\f" => '\f', # form feed (FF)
- "\b" => '\b', # backspace (BS)
- "\a" => '\a', # alarm (bell) (BEL)
- "\e" => '\e', # escape (ESC)
- "\013" => '\v', # vertical tab (VT)
- "\000" => '\0', # nul character (NUL)
- );
+ "\t" => '\t', # tab (HT)
+ "\n" => '\n', # line feed (LF)
+ "\r" => '\r', # carrige return (CR)
+ "\f" => '\f', # form feed (FF)
+ "\b" => '\b', # backspace (BS)
+ "\a" => '\a', # alarm (bell) (BEL)
+ "\e" => '\e', # escape (ESC)
+ "\013" => '\v', # vertical tab (VT)
+ "\000" => '\0', # nul character (NUL)
+ );
my $chr = ( (exists $es{$cntrl})
? $es{$cntrl}
: sprintf('\%03o', ord($cntrl)) );
- return "<span class=\"cntrl\">$chr</span>";
+ if ($opts{-nohtml}) {
+ return $chr;
+ } else {
+ return "<span class=\"cntrl\">$chr</span>";
+ }
}
# Alternatively use unicode control pictures codepoints,
# Unicode "printable representation" (PR)
sub quot_upr {
my $cntrl = shift;
+ my %opts = @_;
+
my $chr = sprintf('&#%04d;', 0x2400+ord($cntrl));
- return "<span class=\"cntrl\">$chr</span>";
+ if ($opts{-nohtml}) {
+ return $chr;
+ } else {
+ return "<span class=\"cntrl\">$chr</span>";
+ }
}
# git may return quoted and escaped filenames
return chr(oct($seq));
} elsif (exists $es{$seq}) {
# C escape sequence, aka character escape code
- return $es{$seq}
+ return $es{$seq};
}
# quoted ordinary character
return $seq;
if ($chopped eq $str) {
return esc_html($chopped);
} else {
- return qq{<span title="} . esc_html($str) . qq{">} .
- esc_html($chopped) . qq{</span>};
+ $str =~ s/([[:cntrl:]])/?/g;
+ return $cgi->span({-title=>$str}, esc_html($chopped));
}
}
my $owner;
return undef unless $project;
+ $git_dir = "$projectroot/$project";
if (!defined $gitweb_project_owner) {
git_get_project_list_from_file();
if (exists $gitweb_project_owner->{$project}) {
$owner = $gitweb_project_owner->{$project};
}
+ if (!defined $owner){
+ $owner = git_get_project_config('owner');
+ }
if (!defined $owner) {
- $owner = get_file_owner("$projectroot/$project");
+ $owner = get_file_owner("$git_dir");
}
return $owner;
static int git_help_config(const char *var, const char *value)
{
- if (!strcmp(var, "help.format")) {
- if (!value)
- return config_error_nonbool(var);
- help_default_format = xstrdup(value);
- return 0;
- }
+ if (!strcmp(var, "help.format"))
+ return git_config_string(&help_default_format, var, value);
return git_default_config(var, value);
}
init_tree_desc(&desc, tree->buffer, tree->size);
- while (tree_entry(&desc, &entry)) {
- if (S_ISDIR(entry.mode))
+ while (tree_entry(&desc, &entry))
+ switch (object_type(entry.mode)) {
+ case OBJ_TREE:
p = process_tree(lookup_tree(entry.sha1), p, &me, name);
- else
+ break;
+ case OBJ_BLOB:
p = process_blob(lookup_blob(entry.sha1), p, &me, name);
- }
+ break;
+ default:
+ /* Subproject commit - not in this repository */
+ break;
+ }
+
free(tree->buffer);
tree->buffer = NULL;
return p;
/* Generate a list of objects that need to be pushed */
pushing = 0;
- prepare_revision_walk(&revs);
+ if (prepare_revision_walk(&revs))
+ die("revision walk setup failed");
mark_edges_uninteresting(revs.commits);
objects_to_send = get_delta(&revs, ref_lock);
finish_all_active_slots();
fill_active_slots();
add_fill_function(NULL, fill_active_slot);
#endif
- finish_all_active_slots();
+ do {
+ finish_all_active_slots();
+#ifdef USE_CURL_MULTI
+ fill_active_slots();
+#endif
+ } while (request_queue_head && !aborted);
/* Update the remote branch if all went well */
- if (aborted || !update_remote(ref->new_sha1, ref_lock)) {
+ if (aborted || !update_remote(ref->new_sha1, ref_lock))
rc = 1;
- goto unlock;
- }
- unlock:
if (!rc)
fprintf(stderr, " done\n");
unlock_remote(ref_lock);
if (!revs->blob_objects)
return;
+ if (!obj)
+ die("bad blob object");
if (obj->flags & (UNINTERESTING | SEEN))
return;
obj->flags |= SEEN;
if (!revs->tree_objects)
return;
+ if (!obj)
+ die("bad tree object");
if (obj->flags & (UNINTERESTING | SEEN))
return;
if (parse_tree(tree) < 0)
opt->loginfo = NULL;
if (!opt->verbose_header) {
- if (opt->left_right) {
- if (commit->object.flags & BOUNDARY)
- putchar('-');
- else if (commit->object.flags & SYMMETRIC_LEFT)
+ if (commit->object.flags & BOUNDARY)
+ putchar('-');
+ else if (commit->object.flags & UNINTERESTING)
+ putchar('^');
+ else if (opt->left_right) {
+ if (commit->object.flags & SYMMETRIC_LEFT)
putchar('<');
else
putchar('>');
fputs("commit ", stdout);
if (commit->object.flags & BOUNDARY)
putchar('-');
+ else if (commit->object.flags & UNINTERESTING)
+ putchar('^');
else if (opt->left_right) {
if (commit->object.flags & SYMMETRIC_LEFT)
putchar('<');
}
}
+ if (!commit->buffer)
+ return;
+
/*
* And then the pretty-printed message itself
*/
if (get_sha1(ref, sha1))
die("Could not resolve ref '%s'", ref);
object = deref_tag(parse_object(sha1), ref, strlen(ref));
+ if (!object)
+ return NULL;
if (object->type == OBJ_TREE)
return make_virtual_commit((struct tree*)object,
better_branch_name(ref));
/* return in the child */
if (!pid) {
dup2(fd[1], 1);
+ dup2(fd[1], 2);
close(fd[0]);
close(fd[1]);
return;
return out;
}
-static void format_person_part(struct strbuf *sb, char part,
+static size_t format_person_part(struct strbuf *sb, char part,
const char *msg, int len)
{
+ /* currently all placeholders have same length */
+ const int placeholder_len = 2;
int start, end, tz = 0;
- unsigned long date;
+ unsigned long date = 0;
char *ep;
- /* parse name */
+ /* advance 'end' to point to email start delimiter */
for (end = 0; end < len && msg[end] != '<'; end++)
; /* do nothing */
+
/*
- * If it does not even have a '<' and '>', that is
- * quite a bogus commit author and we discard it;
- * this is in line with add_user_info() that is used
- * in the normal codepath. When end points at the '<'
- * that we found, it should have matching '>' later,
- * which means start (beginning of email address) must
- * be strictly below len.
+ * When end points at the '<' that we found, it should have
+ * matching '>' later, which means 'end' must be strictly
+ * below len - 1.
*/
- start = end + 1;
- if (start >= len - 1)
- return;
- while (end > 0 && isspace(msg[end - 1]))
- end--;
+ if (end >= len - 2)
+ goto skip;
+
if (part == 'n') { /* name */
+ while (end > 0 && isspace(msg[end - 1]))
+ end--;
strbuf_add(sb, msg, end);
- return;
+ return placeholder_len;
}
+ start = ++end; /* save email start position */
- /* parse email */
- for (end = start; end < len && msg[end] != '>'; end++)
+ /* advance 'end' to point to email end delimiter */
+ for ( ; end < len && msg[end] != '>'; end++)
; /* do nothing */
if (end >= len)
- return;
+ goto skip;
if (part == 'e') { /* email */
strbuf_add(sb, msg + start, end - start);
- return;
+ return placeholder_len;
}
- /* parse date */
+ /* advance 'start' to point to date start delimiter */
for (start = end + 1; start < len && isspace(msg[start]); start++)
; /* do nothing */
if (start >= len)
- return;
+ goto skip;
date = strtoul(msg + start, &ep, 10);
if (msg + start == ep)
- return;
+ goto skip;
if (part == 't') { /* date, UNIX timestamp */
strbuf_add(sb, msg + start, ep - (msg + start));
- return;
+ return placeholder_len;
}
/* parse tz */
switch (part) {
case 'd': /* date */
strbuf_addstr(sb, show_date(date, tz, DATE_NORMAL));
- return;
+ return placeholder_len;
case 'D': /* date, RFC2822 style */
strbuf_addstr(sb, show_date(date, tz, DATE_RFC2822));
- return;
+ return placeholder_len;
case 'r': /* date, relative */
strbuf_addstr(sb, show_date(date, tz, DATE_RELATIVE));
- return;
+ return placeholder_len;
case 'i': /* date, ISO 8601 */
strbuf_addstr(sb, show_date(date, tz, DATE_ISO8601));
- return;
+ return placeholder_len;
}
+
+skip:
+ /*
+ * bogus commit, 'sb' cannot be updated, but we still need to
+ * compute a valid return value.
+ */
+ if (part == 'n' || part == 'e' || part == 't' || part == 'd'
+ || part == 'D' || part == 'r' || part == 'i')
+ return placeholder_len;
+
+ return 0; /* unknown placeholder */
}
struct chunk {
context->commit_header_parsed = 1;
}
-static void format_commit_item(struct strbuf *sb, const char *placeholder,
+static size_t format_commit_item(struct strbuf *sb, const char *placeholder,
void *context)
{
struct format_commit_context *c = context;
/* these are independent of the commit */
switch (placeholder[0]) {
case 'C':
- switch (placeholder[3]) {
- case 'd': /* red */
+ if (!prefixcmp(placeholder + 1, "red")) {
strbuf_addstr(sb, "\033[31m");
- return;
- case 'e': /* green */
+ return 4;
+ } else if (!prefixcmp(placeholder + 1, "green")) {
strbuf_addstr(sb, "\033[32m");
- return;
- case 'u': /* blue */
+ return 6;
+ } else if (!prefixcmp(placeholder + 1, "blue")) {
strbuf_addstr(sb, "\033[34m");
- return;
- case 's': /* reset color */
+ return 5;
+ } else if (!prefixcmp(placeholder + 1, "reset")) {
strbuf_addstr(sb, "\033[m");
- return;
- }
+ return 6;
+ } else
+ return 0;
case 'n': /* newline */
strbuf_addch(sb, '\n');
- return;
+ return 1;
}
/* these depend on the commit */
switch (placeholder[0]) {
case 'H': /* commit hash */
strbuf_addstr(sb, sha1_to_hex(commit->object.sha1));
- return;
+ return 1;
case 'h': /* abbreviated commit hash */
if (add_again(sb, &c->abbrev_commit_hash))
- return;
+ return 1;
strbuf_addstr(sb, find_unique_abbrev(commit->object.sha1,
DEFAULT_ABBREV));
c->abbrev_commit_hash.len = sb->len - c->abbrev_commit_hash.off;
- return;
+ return 1;
case 'T': /* tree hash */
strbuf_addstr(sb, sha1_to_hex(commit->tree->object.sha1));
- return;
+ return 1;
case 't': /* abbreviated tree hash */
if (add_again(sb, &c->abbrev_tree_hash))
- return;
+ return 1;
strbuf_addstr(sb, find_unique_abbrev(commit->tree->object.sha1,
DEFAULT_ABBREV));
c->abbrev_tree_hash.len = sb->len - c->abbrev_tree_hash.off;
- return;
+ return 1;
case 'P': /* parent hashes */
for (p = commit->parents; p; p = p->next) {
if (p != commit->parents)
strbuf_addch(sb, ' ');
strbuf_addstr(sb, sha1_to_hex(p->item->object.sha1));
}
- return;
+ return 1;
case 'p': /* abbreviated parent hashes */
if (add_again(sb, &c->abbrev_parent_hashes))
- return;
+ return 1;
for (p = commit->parents; p; p = p->next) {
if (p != commit->parents)
strbuf_addch(sb, ' ');
}
c->abbrev_parent_hashes.len = sb->len -
c->abbrev_parent_hashes.off;
- return;
+ return 1;
case 'm': /* left/right/bottom */
strbuf_addch(sb, (commit->object.flags & BOUNDARY)
? '-'
: (commit->object.flags & SYMMETRIC_LEFT)
? '<'
: '>');
- return;
+ return 1;
}
/* For the rest we have to parse the commit header. */
parse_commit_header(c);
switch (placeholder[0]) {
- case 's':
+ case 's': /* subject */
strbuf_add(sb, msg + c->subject.off, c->subject.len);
- return;
- case 'a':
- format_person_part(sb, placeholder[1],
+ return 1;
+ case 'a': /* author ... */
+ return format_person_part(sb, placeholder[1],
msg + c->author.off, c->author.len);
- return;
- case 'c':
- format_person_part(sb, placeholder[1],
+ case 'c': /* committer ... */
+ return format_person_part(sb, placeholder[1],
msg + c->committer.off, c->committer.len);
- return;
- case 'e':
+ case 'e': /* encoding */
strbuf_add(sb, msg + c->encoding.off, c->encoding.len);
- return;
- case 'b':
+ return 1;
+ case 'b': /* body */
strbuf_addstr(sb, msg + c->body_off);
- return;
+ return 1;
}
+ return 0; /* unknown placeholder */
}
void format_commit_message(const struct commit *commit,
const void *format, struct strbuf *sb)
{
- const char *placeholders[] = {
- "H", /* commit hash */
- "h", /* abbreviated commit hash */
- "T", /* tree hash */
- "t", /* abbreviated tree hash */
- "P", /* parent hashes */
- "p", /* abbreviated parent hashes */
- "an", /* author name */
- "ae", /* author email */
- "ad", /* author date */
- "aD", /* author date, RFC2822 style */
- "ar", /* author date, relative */
- "at", /* author date, UNIX timestamp */
- "ai", /* author date, ISO 8601 */
- "cn", /* committer name */
- "ce", /* committer email */
- "cd", /* committer date */
- "cD", /* committer date, RFC2822 style */
- "cr", /* committer date, relative */
- "ct", /* committer date, UNIX timestamp */
- "ci", /* committer date, ISO 8601 */
- "e", /* encoding */
- "s", /* subject */
- "b", /* body */
- "Cred", /* red */
- "Cgreen", /* green */
- "Cblue", /* blue */
- "Creset", /* reset color */
- "n", /* newline */
- "m", /* left/right/bottom */
- NULL
- };
struct format_commit_context context;
memset(&context, 0, sizeof(context));
context.commit = commit;
- strbuf_expand(sb, format, placeholders, format_commit_item, &context);
+ strbuf_expand(sb, format, format_commit_item, &context);
}
static void pp_header(enum cmit_fmt fmt,
{
struct object *obj = &blob->object;
+ if (!blob)
+ die("bad blob object");
if (obj->flags & SEEN)
return;
obj->flags |= SEEN;
struct name_entry entry;
struct name_path me;
+ if (!tree)
+ die("bad tree object");
if (obj->flags & SEEN)
return;
obj->flags |= SEEN;
if (parse_tag(tag) < 0)
die("bad tag object %s", sha1_to_hex(obj->sha1));
- add_object(tag->tagged, p, NULL, name);
+ if (tag->tagged)
+ add_object(tag->tagged, p, NULL, name);
}
static void walk_commit_list(struct rev_info *revs)
static void add_one_tree(const unsigned char *sha1, struct rev_info *revs)
{
struct tree *tree = lookup_tree(sha1);
- add_pending_object(revs, &tree->object, "");
+ if (tree)
+ add_pending_object(revs, &tree->object, "");
}
static void add_cache_tree(struct cache_tree *it, struct rev_info *revs)
* Set up the revision walk - this will move all commits
* from the pending list to the commit walking list.
*/
- prepare_revision_walk(revs);
+ if (prepare_revision_walk(revs))
+ die("revision walk setup failed");
walk_commit_list(revs);
}
static void mark_blob_uninteresting(struct blob *blob)
{
+ if (!blob)
+ return;
if (blob->object.flags & UNINTERESTING)
return;
blob->object.flags |= UNINTERESTING;
struct name_entry entry;
struct object *obj = &tree->object;
+ if (!tree)
+ return;
if (obj->flags & UNINTERESTING)
return;
obj->flags |= UNINTERESTING;
struct tag *tag = (struct tag *) object;
if (revs->tag_objects && !(flags & UNINTERESTING))
add_pending_object(revs, object, tag->tag);
+ if (!tag->tagged)
+ die("bad tag");
object = parse_object(tag->tagged->sha1);
if (!object)
die("bad object %s", sha1_to_hex(tag->tagged->sha1));
free_patch_ids(&ids);
}
+static void add_to_list(struct commit_list **p, struct commit *commit, struct commit_list *n)
+{
+ p = &commit_list_insert(commit, p)->next;
+ *p = n;
+}
+
static int limit_list(struct rev_info *revs)
{
struct commit_list *list = revs->commits;
return -1;
if (obj->flags & UNINTERESTING) {
mark_parents_uninteresting(commit);
- if (everybody_uninteresting(list))
+ if (everybody_uninteresting(list)) {
+ if (revs->show_all)
+ add_to_list(p, commit, list);
break;
- continue;
+ }
+ if (!revs->show_all)
+ continue;
}
if (revs->min_age != -1 && (commit->date > revs->min_age))
continue;
it = get_reference(revs, arg, sha1, 0);
if (it->type != OBJ_TAG)
break;
+ if (!((struct tag*)it)->tagged)
+ return 0;
hashcpy(sha1, ((struct tag*)it)->tagged->sha1);
}
if (it->type != OBJ_COMMIT)
revs->dense = 0;
continue;
}
+ if (!strcmp(arg, "--show-all")) {
+ revs->show_all = 1;
+ continue;
+ }
if (!strcmp(arg, "--remove-empty")) {
revs->remove_empty_trees = 1;
continue;
return commit_ignore;
if (revs->unpacked && has_sha1_pack(commit->object.sha1, revs->ignore_packed))
return commit_ignore;
+ if (revs->show_all)
+ return commit_show;
if (commit->object.flags & UNINTERESTING)
return commit_ignore;
if (revs->min_age != -1 && (commit->date > revs->min_age))
prune:1,
no_merges:1,
no_walk:1,
+ show_all:1,
remove_empty_trees:1,
simplify_history:1,
lifo:1,
static int inside_git_dir = -1;
static int inside_work_tree = -1;
-const char *prefix_path(const char *prefix, int len, const char *path)
+static int sanitary_path_copy(char *dst, const char *src)
{
- const char *orig = path;
+ char *dst0 = dst;
+
+ if (*src == '/') {
+ *dst++ = '/';
+ while (*src == '/')
+ src++;
+ }
+
for (;;) {
- char c;
- if (*path != '.')
- break;
- c = path[1];
- /* "." */
- if (!c) {
- path++;
- break;
+ char c = *src;
+
+ /*
+ * A path component that begins with . could be
+ * special:
+ * (1) "." and ends -- ignore and terminate.
+ * (2) "./" -- ignore them, eat slash and continue.
+ * (3) ".." and ends -- strip one and terminate.
+ * (4) "../" -- strip one, eat slash and continue.
+ */
+ if (c == '.') {
+ switch (src[1]) {
+ case '\0':
+ /* (1) */
+ src++;
+ break;
+ case '/':
+ /* (2) */
+ src += 2;
+ while (*src == '/')
+ src++;
+ continue;
+ case '.':
+ switch (src[2]) {
+ case '\0':
+ /* (3) */
+ src += 2;
+ goto up_one;
+ case '/':
+ /* (4) */
+ src += 3;
+ while (*src == '/')
+ src++;
+ goto up_one;
+ }
+ }
}
- /* "./" */
+
+ /* copy up to the next '/', and eat all '/' */
+ while ((c = *src++) != '\0' && c != '/')
+ *dst++ = c;
if (c == '/') {
- path += 2;
- continue;
- }
- if (c != '.')
+ *dst++ = c;
+ while (c == '/')
+ c = *src++;
+ src--;
+ } else if (!c)
break;
- c = path[2];
- if (!c)
- path += 2;
- else if (c == '/')
- path += 3;
- else
- break;
- /* ".." and "../" */
- /* Remove last component of the prefix */
- do {
- if (!len)
- die("'%s' is outside repository", orig);
- len--;
- } while (len && prefix[len-1] != '/');
continue;
+
+ up_one:
+ /*
+ * dst0..dst is prefix portion, and dst[-1] is '/';
+ * go up one level.
+ */
+ dst -= 2; /* go past trailing '/' if any */
+ if (dst < dst0)
+ return -1;
+ while (1) {
+ if (dst <= dst0)
+ break;
+ c = *dst--;
+ if (c == '/') {
+ dst += 2;
+ break;
+ }
+ }
}
- if (len) {
- int speclen = strlen(path);
- char *n = xmalloc(speclen + len + 1);
+ *dst = '\0';
+ return 0;
+}
- memcpy(n, prefix, len);
- memcpy(n + len, path, speclen+1);
- path = n;
+const char *prefix_path(const char *prefix, int len, const char *path)
+{
+ const char *orig = path;
+ char *sanitized = xmalloc(len + strlen(path) + 1);
+ if (*orig == '/')
+ strcpy(sanitized, path);
+ else {
+ if (len)
+ memcpy(sanitized, prefix, len);
+ strcpy(sanitized + len, path);
}
- return path;
+ if (sanitary_path_copy(sanitized, sanitized))
+ goto error_out;
+ if (*orig == '/') {
+ const char *work_tree = get_git_work_tree();
+ size_t len = strlen(work_tree);
+ size_t total = strlen(sanitized) + 1;
+ if (strncmp(sanitized, work_tree, len) ||
+ (sanitized[len] != '\0' && sanitized[len] != '/')) {
+ error_out:
+ error("'%s' is outside repository", orig);
+ free(sanitized);
+ return NULL;
+ }
+ if (sanitized[len] == '/')
+ len++;
+ memmove(sanitized, sanitized + len, total - len);
+ }
+ return sanitized;
}
/*
const char **get_pathspec(const char *prefix, const char **pathspec)
{
const char *entry = *pathspec;
- const char **p;
+ const char **src, **dst;
int prefixlen;
if (!prefix && !entry)
}
/* Otherwise we have to re-write the entries.. */
- p = pathspec;
+ src = pathspec;
+ dst = pathspec;
prefixlen = prefix ? strlen(prefix) : 0;
- do {
- *p = prefix_path(prefix, prefixlen, entry);
- } while ((entry = *++p) != NULL);
- return (const char **) pathspec;
+ while (*src) {
+ const char *p = prefix_path(prefix, prefixlen, *src);
+ if (p)
+ *(dst++) = p;
+ src++;
+ }
+ *dst = NULL;
+ if (!*pathspec)
+ return NULL;
+ return pathspec;
}
/*
} *cached_objects;
static int cached_object_nr, cached_object_alloc;
+static struct cached_object empty_tree = {
+ /* empty tree sha1: 4b825dc642cb6eb9a060e54bf8d69288fbee4904 */
+ "\x4b\x82\x5d\xc6\x42\xcb\x6e\xb9\xa0\x60"
+ "\xe5\x4b\xf8\xd6\x92\x88\xfb\xee\x49\x04",
+ OBJ_TREE,
+ "",
+ 0
+};
+
static struct cached_object *find_cached_object(const unsigned char *sha1)
{
int i;
if (!hashcmp(co->sha1, sha1))
return co;
}
+ if (!hashcmp(sha1, empty_tree.sha1))
+ return &empty_tree;
return NULL;
}
}
ref_length = strlen(ref_type);
- if (memcmp(buffer, ref_type, ref_length) ||
+ if (ref_length + 40 > isize ||
+ memcmp(buffer, ref_type, ref_length) ||
get_sha1_hex((char *) buffer + ref_length, actual_sha1)) {
free(buffer);
return NULL;
return error("%.*s: expected %s type, but the object dereferences to %s type",
len, name, typename(expected_type),
typename(o->type));
+ if (!o)
+ return -1;
if (!o->parsed)
- parse_object(o->sha1);
+ if (!parse_object(o->sha1))
+ return -1;
}
}
return 0;
struct object *object = parse_object(sha1);
if (!object)
return 0;
- if (object->type == OBJ_TAG)
+ if (object->type == OBJ_TAG) {
object = deref_tag(object, path, strlen(path));
+ if (!object)
+ return 0;
+ }
if (object->type != OBJ_COMMIT)
return 0;
insert_by_date((struct commit *)object, list);
unsigned long size;
commit = pop_most_recent_commit(&list, ONELINE_SEEN);
- parse_object(commit->object.sha1);
+ if (!parse_object(commit->object.sha1))
+ continue;
if (temp_commit_buffer)
free(temp_commit_buffer);
if (commit->buffer)
if (i < heads->nr) {
commit = (struct commit *)
deref_tag(heads->objects[i++].item, NULL, 0);
- if (commit->object.type != OBJ_COMMIT) {
+ if (!commit || commit->object.type != OBJ_COMMIT) {
commit = NULL;
continue;
}
cur_depth = *(int *)commit->util;
}
}
- parse_commit(commit);
+ if (parse_commit(commit))
+ die("invalid commit");
commit->object.flags |= not_shallow_flag;
cur_depth++;
for (p = commit->parents, commit = NULL; p; p = p->next) {
strbuf_setlen(sb, sb->len + len);
}
-void strbuf_expand(struct strbuf *sb, const char *format,
- const char **placeholders, expand_fn_t fn, void *context)
+void strbuf_expand(struct strbuf *sb, const char *format, expand_fn_t fn,
+ void *context)
{
for (;;) {
- const char *percent, **p;
+ const char *percent;
+ size_t consumed;
percent = strchrnul(format, '%');
strbuf_add(sb, format, percent - format);
break;
format = percent + 1;
- for (p = placeholders; *p; p++) {
- if (!prefixcmp(format, *p))
- break;
- }
- if (*p) {
- fn(sb, *p, context);
- format += strlen(*p);
- } else
+ consumed = fn(sb, format, context);
+ if (consumed)
+ format += consumed;
+ else
strbuf_addch(sb, '%');
}
}
}
extern void strbuf_adddup(struct strbuf *sb, size_t pos, size_t len);
-typedef void (*expand_fn_t) (struct strbuf *sb, const char *placeholder, void *context);
-extern void strbuf_expand(struct strbuf *sb, const char *format, const char **placeholders, expand_fn_t fn, void *context);
+typedef size_t (*expand_fn_t) (struct strbuf *sb, const char *placeholder, void *context);
+extern void strbuf_expand(struct strbuf *sb, const char *format, expand_fn_t fn, void *context);
__attribute__((format(printf,2,3)))
extern void strbuf_addf(struct strbuf *sb, const char *fmt, ...);
--- /dev/null
+#!/bin/sh
+
+test_description='add -i basic tests'
+. ./test-lib.sh
+
+test_expect_success 'setup (initial)' '
+ echo content >file &&
+ git add file &&
+ echo more >>file &&
+ echo lines >>file
+'
+test_expect_success 'status works (initial)' '
+ git add -i </dev/null >output &&
+ grep "+1/-0 *+2/-0 file" output
+'
+cat >expected <<EOF
+new file mode 100644
+index 0000000..d95f3ad
+--- /dev/null
++++ b/file
+@@ -0,0 +1 @@
++content
+EOF
+test_expect_success 'diff works (initial)' '
+ (echo d; echo 1) | git add -i >output &&
+ sed -ne "/new file/,/content/p" <output >diff &&
+ diff -u expected diff
+'
+test_expect_success 'revert works (initial)' '
+ git add file &&
+ (echo r; echo 1) | git add -i &&
+ git ls-files >output &&
+ ! grep . output
+'
+
+test_expect_success 'setup (commit)' '
+ echo baseline >file &&
+ git add file &&
+ git commit -m commit &&
+ echo content >>file &&
+ git add file &&
+ echo more >>file &&
+ echo lines >>file
+'
+test_expect_success 'status works (commit)' '
+ git add -i </dev/null >output &&
+ grep "+1/-0 *+2/-0 file" output
+'
+cat >expected <<EOF
+index 180b47c..b6f2c08 100644
+--- a/file
++++ b/file
+@@ -1 +1,2 @@
+ baseline
++content
+EOF
+test_expect_success 'diff works (commit)' '
+ (echo d; echo 1) | git add -i >output &&
+ sed -ne "/^index/,/content/p" <output >diff &&
+ diff -u expected diff
+'
+test_expect_success 'revert works (commit)' '
+ git add file &&
+ (echo r; echo 1) | git add -i &&
+ git add -i </dev/null >output &&
+ grep "unchanged *+3/-0 file" output
+'
+
+test_done
test 0 = $copied
'
+test_expect_success 'local clone of repo with nonexistent ref in HEAD' '
+ cd "$D" &&
+ echo "ref: refs/heads/nonexistent" > a.git/HEAD &&
+ git clone a d &&
+ cd d &&
+ git fetch &&
+ test ! -e .git/refs/remotes/origin/HEAD'
+
test_done
git mv ab a
'
+test_expect_success 'absolute pathname' '(
+
+ rm -fr mine &&
+ mkdir mine &&
+ cd mine &&
+ test_create_repo one &&
+ cd one &&
+ mkdir sub &&
+ >sub/file &&
+ git add sub/file &&
+
+ git mv sub "$(pwd)/in" &&
+ ! test -d sub &&
+ test -d in &&
+ git ls-files --error-unmatch in/file
+
+
+)'
+
+test_expect_success 'absolute pathname outside should fail' '(
+
+ rm -fr mine &&
+ mkdir mine &&
+ cd mine &&
+ out=$(pwd) &&
+ test_create_repo one &&
+ cd one &&
+ mkdir sub &&
+ >sub/file &&
+ git add sub/file &&
+
+ ! git mv sub "$out/out" &&
+ test -d sub &&
+ ! test -d ../in &&
+ git ls-files --error-unmatch sub/file
+
+)'
+
test_done
--- /dev/null
+#!/bin/sh
+
+test_description='setup taking and sanitizing funny paths'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+
+ mkdir -p a/b/c a/e &&
+ D=$(pwd) &&
+ >a/b/c/d &&
+ >a/e/f
+
+'
+
+test_expect_success 'git add (absolute)' '
+
+ git add "$D/a/b/c/d" &&
+ git ls-files >current &&
+ echo a/b/c/d >expect &&
+ diff -u expect current
+
+'
+
+
+test_expect_success 'git add (funny relative)' '
+
+ rm -f .git/index &&
+ (
+ cd a/b &&
+ git add "../e/./f"
+ ) &&
+ git ls-files >current &&
+ echo a/e/f >expect &&
+ diff -u expect current
+
+'
+
+test_expect_success 'git rm (absolute)' '
+
+ rm -f .git/index &&
+ git add a &&
+ git rm -f --cached "$D/a/b/c/d" &&
+ git ls-files >current &&
+ echo a/e/f >expect &&
+ diff -u expect current
+
+'
+
+test_expect_success 'git rm (funny relative)' '
+
+ rm -f .git/index &&
+ git add a &&
+ (
+ cd a/b &&
+ git rm -f --cached "../e/./f"
+ ) &&
+ git ls-files >current &&
+ echo a/b/c/d >expect &&
+ diff -u expect current
+
+'
+
+test_expect_success 'git ls-files (absolute)' '
+
+ rm -f .git/index &&
+ git add a &&
+ git ls-files "$D/a/e/../b" >current &&
+ echo a/b/c/d >expect &&
+ diff -u expect current
+
+'
+
+test_expect_success 'git ls-files (relative #1)' '
+
+ rm -f .git/index &&
+ git add a &&
+ (
+ cd a/b &&
+ git ls-files "../b/c"
+ ) >current &&
+ echo c/d >expect &&
+ diff -u expect current
+
+'
+
+test_expect_success 'git ls-files (relative #2)' '
+
+ rm -f .git/index &&
+ git add a &&
+ (
+ cd a/b &&
+ git ls-files --full-name "../e/f"
+ ) >current &&
+ echo a/e/f >expect &&
+ diff -u expect current
+
+'
+
+test_expect_success 'git ls-files (relative #3)' '
+
+ rm -f .git/index &&
+ git add a &&
+ (
+ cd a/b &&
+ if git ls-files "../e/f"
+ then
+ echo Gaah, should have failed
+ exit 1
+ else
+ : happy
+ fi
+ )
+
+'
+
+test_expect_success 'commit using absolute path names' '
+ git commit -m "foo" &&
+ echo aa >>a/b/c/d &&
+ git commit -m "aa" "$(pwd)/a/b/c/d"
+'
+
+test_expect_success 'log using absolute path names' '
+ echo bb >>a/b/c/d &&
+ git commit -m "bb" $(pwd)/a/b/c/d &&
+
+ git log a/b/c/d >f1.txt &&
+ git log "$(pwd)/a/b/c/d" >f2.txt &&
+ diff -u f1.txt f2.txt
+'
+
+test_expect_success 'blame using absolute path names' '
+ git blame a/b/c/d >f1.txt &&
+ git blame "$(pwd)/a/b/c/d" >f2.txt &&
+ diff -u f1.txt f2.txt
+'
+
+test_expect_success 'setup deeper work tree' '
+ test_create_repo tester
+'
+
+test_expect_success 'add a directory outside the work tree' '(
+ cd tester &&
+ d1="$(cd .. ; pwd)" &&
+ git add "$d1"
+)'
+
+test_expect_success 'add a file outside the work tree, nasty case 1' '(
+ cd tester &&
+ f="$(pwd)x" &&
+ echo "$f" &&
+ touch "$f" &&
+ git add "$f"
+)'
+
+test_expect_success 'add a file outside the work tree, nasty case 2' '(
+ cd tester &&
+ f="$(pwd | sed "s/.$//")x" &&
+ echo "$f" &&
+ touch "$f" &&
+ git add "$f"
+)'
+
+test_done
)
'
+test_expect_success 'check files before directories' '
+
+ echo Notes > release-notes &&
+ git add release-notes &&
+ git commit -m "Add release notes" release-notes &&
+ id=$(git rev-parse HEAD) &&
+ git cvsexportcommit -w "$CVSWORK" -c $id &&
+
+ echo new > DS &&
+ echo new > E/DS &&
+ echo modified > release-notes &&
+ git add DS E/DS release-notes &&
+ git commit -m "Add two files with the same basename" &&
+ id=$(git rev-parse HEAD) &&
+ git cvsexportcommit -w "$CVSWORK" -c $id &&
+ check_entries "$CVSWORK/E" "DS/1.1/|newfile5.txt/1.1/" &&
+ check_entries "$CVSWORK" "DS/1.1/|release-notes/1.2/" &&
+ diff -u "$CVSWORK/DS" DS &&
+ diff -u "$CVSWORK/E/DS" E/DS &&
+ diff -u "$CVSWORK/release-notes" release-notes
+
+'
+
+test_expect_success 'commit a file with leading spaces in the name' '
+
+ echo space > " space" &&
+ git add " space" &&
+ git commit -m "Add a file with a leading space" &&
+ id=$(git rev-parse HEAD) &&
+ git cvsexportcommit -w "$CVSWORK" -c $id &&
+ check_entries "$CVSWORK" " space/1.1/|DS/1.1/|release-notes/1.2/" &&
+ diff -u "$CVSWORK/ space" " space"
+
+'
+
test_done
struct object *deref_tag(struct object *o, const char *warn, int warnlen)
{
while (o && o->type == OBJ_TAG)
- o = parse_object(((struct tag *)o)->tagged->sha1);
+ if (((struct tag *)o)->tagged)
+ o = parse_object(((struct tag *)o)->tagged->sha1);
+ else
+ o = NULL;
if (!o && warn) {
if (!warnlen)
warnlen = strlen(warn);
}
setup_revisions(0, NULL, &revs, NULL);
}
- prepare_revision_walk(&revs);
+ if (prepare_revision_walk(&revs))
+ die("revision walk setup failed");
mark_edges_uninteresting(revs.commits, &revs, show_edge);
traverse_commit_list(&revs, show_commit, show_object);
return 0;
/* make sure the real parents are parsed */
unregister_shallow(object->sha1);
object->parsed = 0;
- parse_commit((struct commit *)object);
+ if (parse_commit((struct commit *)object))
+ die("invalid commit");
parents = ((struct commit *)object)->parents;
while (parents) {
add_object_array(&parents->item->object,
}
if (o->type == OBJ_TAG) {
o = deref_tag(o, refname, 0);
- packet_write(1, "%s %s^{}\n", sha1_to_hex(o->sha1), refname);
+ if (o)
+ packet_write(1, "%s %s^{}\n", sha1_to_hex(o->sha1), refname);
}
return 0;
}