Skip to content

Commit

Permalink
Add --temp and --stage=all options to checkout-index.
Browse files Browse the repository at this point in the history
Sometimes it is convient for a Porcelain to be able to checkout all
unmerged files in all stages so that an external merge tool can be
executed by the Porcelain or the end-user.  Using git-unpack-file
on each stage individually incurs a rather high penalty due to the
need to fork for each file version obtained.  git-checkout-index -a
--stage=all will now do the same thing, but faster.

Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
Signed-off-by: Junio C Hamano <junkio@cox.net>
  • Loading branch information
spearce authored and Junio C Hamano committed Mar 5, 2006
1 parent 46444f5 commit de84f99
Show file tree
Hide file tree
Showing 7 changed files with 371 additions and 27 deletions.
51 changes: 49 additions & 2 deletions Documentation/git-checkout-index.txt
Expand Up @@ -10,7 +10,8 @@ SYNOPSIS
--------
[verse]
'git-checkout-index' [-u] [-q] [-a] [-f] [-n] [--prefix=<string>]
[--stage=<number>]
[--stage=<number>|all]
[--temp]
[-z] [--stdin]
[--] [<file>]\*

Expand Down Expand Up @@ -43,9 +44,15 @@ OPTIONS
When creating files, prepend <string> (usually a directory
including a trailing /)

--stage=<number>::
--stage=<number>|all::
Instead of checking out unmerged entries, copy out the
files from named stage. <number> must be between 1 and 3.
Note: --stage=all automatically implies --temp.

--temp::
Instead of copying the files to the working directory
write the content to temporary files. The temporary name
associations will be written to stdout.

--stdin::
Instead of taking list of paths from the command line,
Expand Down Expand Up @@ -87,6 +94,46 @@ it will prevent problems with a filename of, for example, `-a`.
Using `--` is probably a good policy in scripts.


Using --temp or --stage=all
---------------------------
When `--temp` is used (or implied by `--stage=all`)
`git-checkout-index` will create a temporary file for each index
entry being checked out. The index will not be updated with stat
information. These options can be useful if the caller needs all
stages of all unmerged entries so that the unmerged files can be
processed by an external merge tool.

A listing will be written to stdout providing the association of
temporary file names to tracked path names. The listing format
has two variations:

. tempname TAB path RS
+
The first format is what gets used when `--stage` is omitted or
is not `--stage=all`. The field tempname is the temporary file
name holding the file content and path is the tracked path name in
the index. Only the requested entries are output.

. stage1temp SP stage2temp SP stage3tmp TAB path RS
+
The second format is what gets used when `--stage=all`. The three
stage temporary fields (stage1temp, stage2temp, stage3temp) list the
name of the temporary file if there is a stage entry in the index
or `.` if there is no stage entry. Paths which only have a stage 0
entry will always be omitted from the output.

In both formats RS (the record separator) is newline by default
but will be the null byte if -z was passed on the command line.
The temporary file names are always safe strings; they will never
contain directory separators or whitespace characters. The path
field is always relative to the current directory and the temporary
file names are always relative to the top level directory.

If the object being copied out to a temporary file is a symbolic
link the content of the link will be written to a normal file. It is
up to the end-user or the Porcelain to make use of this information.


EXAMPLES
--------
To update and refresh only the files already checked out::
Expand Down
3 changes: 2 additions & 1 deletion apply.c
Expand Up @@ -1402,7 +1402,8 @@ static int check_patch(struct patch *patch)
costate.not_new = 0;
costate.refresh_cache = 1;
if (checkout_entry(active_cache[pos],
&costate) ||
&costate,
NULL) ||
lstat(old_name, &st))
return -1;
}
Expand Down
2 changes: 1 addition & 1 deletion cache.h
Expand Up @@ -262,7 +262,7 @@ struct checkout {
refresh_cache:1;
};

extern int checkout_entry(struct cache_entry *ce, struct checkout *state);
extern int checkout_entry(struct cache_entry *ce, struct checkout *state, char *topath);

extern struct alternate_object_database {
struct alternate_object_database *next;
Expand Down
86 changes: 74 additions & 12 deletions checkout-index.c
Expand Up @@ -40,9 +40,13 @@
#include "strbuf.h"
#include "quote.h"

#define CHECKOUT_ALL 4
static const char *prefix;
static int prefix_length;
static int line_termination = '\n';
static int checkout_stage; /* default to checkout stage0 */
static int to_tempfile;
static char topath[4][MAXPATHLEN+1];

static struct checkout state = {
.base_dir = "",
Expand All @@ -53,11 +57,39 @@ static struct checkout state = {
.refresh_cache = 0,
};

static void write_tempfile_record (const char *name)
{
int i;

if (CHECKOUT_ALL == checkout_stage) {
for (i = 1; i < 4; i++) {
if (i > 1)
putchar(' ');
if (topath[i][0])
fputs(topath[i], stdout);
else
putchar('.');
}
} else
fputs(topath[checkout_stage], stdout);

putchar('\t');
write_name_quoted("", 0, name + prefix_length,
line_termination, stdout);
putchar(line_termination);

for (i = 0; i < 4; i++) {
topath[i][0] = 0;
}
}

static int checkout_file(const char *name)
{
int namelen = strlen(name);
int pos = cache_name_pos(name, namelen);
int has_same_name = 0;
int did_checkout = 0;
int errs = 0;

if (pos < 0)
pos = -pos - 1;
Expand All @@ -68,9 +100,20 @@ static int checkout_file(const char *name)
memcmp(ce->name, name, namelen))
break;
has_same_name = 1;
if (checkout_stage == ce_stage(ce))
return checkout_entry(ce, &state);
pos++;
if (ce_stage(ce) != checkout_stage
&& (CHECKOUT_ALL != checkout_stage || !ce_stage(ce)))
continue;
did_checkout = 1;
if (checkout_entry(ce, &state,
to_tempfile ? topath[ce_stage(ce)] : NULL) < 0)
errs++;
}

if (did_checkout) {
if (to_tempfile)
write_tempfile_record(name);
return errs > 0 ? -1 : 0;
}

if (!state.quiet) {
Expand All @@ -90,18 +133,29 @@ static int checkout_file(const char *name)
static int checkout_all(void)
{
int i, errs = 0;
struct cache_entry* last_ce = 0;

for (i = 0; i < active_nr ; i++) {
struct cache_entry *ce = active_cache[i];
if (ce_stage(ce) != checkout_stage)
if (ce_stage(ce) != checkout_stage
&& (CHECKOUT_ALL != checkout_stage || !ce_stage(ce)))
continue;
if (prefix && *prefix &&
(ce_namelen(ce) <= prefix_length ||
memcmp(prefix, ce->name, prefix_length)))
continue;
if (checkout_entry(ce, &state) < 0)
if (last_ce && to_tempfile) {
if (ce_namelen(last_ce) != ce_namelen(ce)
|| memcmp(last_ce->name, ce->name, ce_namelen(ce)))
write_tempfile_record(last_ce->name);
}
if (checkout_entry(ce, &state,
to_tempfile ? topath[ce_stage(ce)] : NULL) < 0)
errs++;
last_ce = ce;
}
if (last_ce && to_tempfile)
write_tempfile_record(last_ce->name);
if (errs)
/* we have already done our error reporting.
* exit with the same code as die().
Expand All @@ -111,7 +165,7 @@ static int checkout_all(void)
}

static const char checkout_cache_usage[] =
"git-checkout-index [-u] [-q] [-a] [-f] [-n] [--stage=[123]] [--prefix=<string>] [--] <file>...";
"git-checkout-index [-u] [-q] [-a] [-f] [-n] [--stage=[123]|all] [--prefix=<string>] [--temp] [--] <file>...";

static struct cache_file cache_file;

Expand All @@ -121,7 +175,6 @@ int main(int argc, char **argv)
int newfd = -1;
int all = 0;
int read_from_stdin = 0;
int line_termination = '\n';

prefix = setup_git_directory();
git_config(git_default_config);
Expand Down Expand Up @@ -175,25 +228,34 @@ int main(int argc, char **argv)
i++; /* do not consider arg as a file name */
break;
}
if (!strcmp(arg, "--temp")) {
to_tempfile = 1;
continue;
}
if (!strncmp(arg, "--prefix=", 9)) {
state.base_dir = arg+9;
state.base_dir_len = strlen(state.base_dir);
continue;
}
if (!strncmp(arg, "--stage=", 8)) {
int ch = arg[8];
if ('1' <= ch && ch <= '3')
checkout_stage = arg[8] - '0';
else
die("stage should be between 1 and 3");
if (!strcmp(arg + 8, "all")) {
to_tempfile = 1;
checkout_stage = CHECKOUT_ALL;
} else {
int ch = arg[8];
if ('1' <= ch && ch <= '3')
checkout_stage = arg[8] - '0';
else
die("stage should be between 1 and 3 or all");
}
continue;
}
if (arg[0] == '-')
usage(checkout_cache_usage);
break;
}

if (state.base_dir_len) {
if (state.base_dir_len || to_tempfile) {
/* when --prefix is specified we do not
* want to update cache.
*/
Expand Down
42 changes: 32 additions & 10 deletions entry.c
Expand Up @@ -63,7 +63,7 @@ static int create_file(const char *path, unsigned int mode)
return open(path, O_WRONLY | O_CREAT | O_EXCL, mode);
}

static int write_entry(struct cache_entry *ce, const char *path, struct checkout *state)
static int write_entry(struct cache_entry *ce, char *path, struct checkout *state, int to_tempfile)
{
int fd;
void *new;
Expand All @@ -80,7 +80,11 @@ static int write_entry(struct cache_entry *ce, const char *path, struct checkout
}
switch (ntohl(ce->ce_mode) & S_IFMT) {
case S_IFREG:
fd = create_file(path, ntohl(ce->ce_mode));
if (to_tempfile) {
strcpy(path, ".merge_file_XXXXXX");
fd = mkstemp(path);
} else
fd = create_file(path, ntohl(ce->ce_mode));
if (fd < 0) {
free(new);
return error("git-checkout-index: unable to create file %s (%s)",
Expand All @@ -93,12 +97,27 @@ static int write_entry(struct cache_entry *ce, const char *path, struct checkout
return error("git-checkout-index: unable to write file %s", path);
break;
case S_IFLNK:
if (symlink(new, path)) {
if (to_tempfile) {
strcpy(path, ".merge_link_XXXXXX");
fd = mkstemp(path);
if (fd < 0) {
free(new);
return error("git-checkout-index: unable to create "
"file %s (%s)", path, strerror(errno));
}
wrote = write(fd, new, size);
close(fd);
free(new);
if (wrote != size)
return error("git-checkout-index: unable to write file %s",
path);
} else {
wrote = symlink(new, path);
free(new);
return error("git-checkout-index: unable to create "
"symlink %s (%s)", path, strerror(errno));
if (wrote)
return error("git-checkout-index: unable to create "
"symlink %s (%s)", path, strerror(errno));
}
free(new);
break;
default:
free(new);
Expand All @@ -113,12 +132,15 @@ static int write_entry(struct cache_entry *ce, const char *path, struct checkout
return 0;
}

int checkout_entry(struct cache_entry *ce, struct checkout *state)
int checkout_entry(struct cache_entry *ce, struct checkout *state, char *topath)
{
struct stat st;
static char path[MAXPATHLEN+1];
struct stat st;
int len = state->base_dir_len;

if (topath)
return write_entry(ce, topath, state, 1);

memcpy(path, state->base_dir, len);
strcpy(path + len, ce->name);

Expand All @@ -144,10 +166,10 @@ int checkout_entry(struct cache_entry *ce, struct checkout *state)
return error("%s is a directory", path);
remove_subtree(path);
}
} else if (state->not_new)
} else if (state->not_new)
return 0;
create_directories(path, state);
return write_entry(ce, path, state);
return write_entry(ce, path, state, 0);
}


2 changes: 1 addition & 1 deletion read-tree.c
Expand Up @@ -337,7 +337,7 @@ static void check_updates(struct cache_entry **src, int nr)
if (ce->ce_flags & mask) {
ce->ce_flags &= ~mask;
if (update)
checkout_entry(ce, &state);
checkout_entry(ce, &state, NULL);
}
}
if (total) {
Expand Down

0 comments on commit de84f99

Please sign in to comment.