Skip to content

Commit ea96357

Browse files
bk2204gitster
authored andcommitted
builtin/stash: provide a way to export stashes to a ref
A common user problem is how to sync in-progress work to another machine. Users currently must use some sort of transfer of the working tree, which poses security risks and also necessarily causes the index to become dirty. The experience is suboptimal and frustrating for users. A reasonable idea is to use the stash for this purpose, but the stash is stored in the reflog, not in a ref, and as such it cannot be pushed or pulled. This also means that it cannot be saved into a bundle or preserved elsewhere, which is a problem when using throwaway development environments. Let's solve this problem by allowing the user to export the stash to a ref (or, to just write it into the repository and print the hash, à la git commit-tree). Introduce git stash export, which writes a chain of commits where the first parent is always a chain to the previous stash, or to a single, empty commit (for the final item) and the second is the stash commit normally written to the reflog. Iterate over each stash from topmost to bottomost, looking up the data for each one, and then create the chain from the single empty commit back up in reverse order. Generate a predictable empty commit so our behavior is reproducible. Create a useful commit message, preserving the author and committer information, to help users identify stash commits when viewing them as normal commits. If the user has specified specific stashes they'd like to export instead, use those instead of iterating over all of the stashes. As part of this, specifically request quiet behavior when looking up the OID for a revision because we will eventually hit a revision that doesn't exist and we don't want to die when that occurs. Signed-off-by: brian m. carlson <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 1e64828 commit ea96357

File tree

2 files changed

+223
-1
lines changed

2 files changed

+223
-1
lines changed

Documentation/git-stash.adoc

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ SYNOPSIS
2323
'git stash' clear
2424
'git stash' create [<message>]
2525
'git stash' store [(-m | --message) <message>] [-q | --quiet] <commit>
26+
'git stash' export (--print | --to-ref <ref>) [<stash>...]
2627

2728
DESCRIPTION
2829
-----------
@@ -154,6 +155,12 @@ store::
154155
reflog. This is intended to be useful for scripts. It is
155156
probably not the command you want to use; see "push" above.
156157

158+
export ( --print | --to-ref <ref> ) [<stash>...]::
159+
160+
Export the specified stashes, or all of them if none are specified, to
161+
a chain of commits which can be transferred using the normal fetch and
162+
push mechanisms, then imported using the `import` subcommand.
163+
157164
OPTIONS
158165
-------
159166
-a::
@@ -242,6 +249,19 @@ literally (including newlines and quotes).
242249
+
243250
Quiet, suppress feedback messages.
244251
252+
--print::
253+
This option is only valid for `export`.
254+
+
255+
Create the chain of commits representing the exported stashes without
256+
storing it anywhere in the ref namespace and print the object ID to
257+
standard output. This is designed for scripts.
258+
259+
--to-ref::
260+
This option is only valid for `export`.
261+
+
262+
Create the chain of commits representing the exported stashes and store
263+
it to the specified ref.
264+
245265
\--::
246266
This option is only valid for `push` command.
247267
+
@@ -259,7 +279,7 @@ For more details, see the 'pathspec' entry in linkgit:gitglossary[7].
259279
260280
<stash>::
261281
This option is only valid for `apply`, `branch`, `drop`, `pop`,
262-
`show` commands.
282+
`show`, and `export` commands.
263283
+
264284
A reference of the form `stash@{<revision>}`. When no `<stash>` is
265285
given, the latest stash is assumed (that is, `stash@{0}`).

builtin/stash.c

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
#include "diffcore.h"
3030
#include "reflog.h"
3131
#include "add-interactive.h"
32+
#include "oid-array.h"
33+
#include "commit.h"
3234

3335
#define INCLUDE_ALL_FILES 2
3436

@@ -56,6 +58,8 @@
5658
" [-u | --include-untracked] [-a | --all] [<message>]")
5759
#define BUILTIN_STASH_CREATE_USAGE \
5860
N_("git stash create [<message>]")
61+
#define BUILTIN_STASH_EXPORT_USAGE \
62+
N_("git stash export (--print | --to-ref <ref>) [<stash>...]")
5963
#define BUILTIN_STASH_CLEAR_USAGE \
6064
"git stash clear"
6165

@@ -71,6 +75,7 @@ static const char * const git_stash_usage[] = {
7175
BUILTIN_STASH_CLEAR_USAGE,
7276
BUILTIN_STASH_CREATE_USAGE,
7377
BUILTIN_STASH_STORE_USAGE,
78+
BUILTIN_STASH_EXPORT_USAGE,
7479
NULL
7580
};
7681

@@ -124,6 +129,12 @@ static const char * const git_stash_save_usage[] = {
124129
NULL
125130
};
126131

132+
static const char * const git_stash_export_usage[] = {
133+
BUILTIN_STASH_EXPORT_USAGE,
134+
NULL
135+
};
136+
137+
127138
static const char ref_stash[] = "refs/stash";
128139
static struct strbuf stash_index_path = STRBUF_INIT;
129140

@@ -1896,6 +1907,196 @@ static int save_stash(int argc, const char **argv, const char *prefix,
18961907
return ret;
18971908
}
18981909

1910+
static int write_commit_with_parents(struct repository *r,
1911+
struct object_id *out,
1912+
const struct object_id *oid,
1913+
struct commit_list *parents)
1914+
{
1915+
size_t author_len, committer_len;
1916+
struct commit *this;
1917+
const char *orig_author, *orig_committer;
1918+
char *author = NULL, *committer = NULL;
1919+
const char *buffer;
1920+
unsigned long bufsize;
1921+
const char *p;
1922+
struct strbuf msg = STRBUF_INIT;
1923+
int ret = 0;
1924+
struct ident_split id;
1925+
1926+
this = lookup_commit_reference(r, oid);
1927+
buffer = repo_get_commit_buffer(r, this, &bufsize);
1928+
orig_author = find_commit_header(buffer, "author", &author_len);
1929+
orig_committer = find_commit_header(buffer, "committer", &committer_len);
1930+
if (split_ident_line(&id, orig_author, author_len) < 0 ||
1931+
split_ident_line(&id, orig_committer, committer_len) < 0) {
1932+
ret = error(_("invalid author or committer for %s"), oid_to_hex(oid));
1933+
goto out;
1934+
}
1935+
p = strstr(buffer, "\n\n");
1936+
1937+
if (!orig_author || !orig_committer || !p) {
1938+
ret = error(_("cannot parse commit %s"), oid_to_hex(oid));
1939+
goto out;
1940+
}
1941+
/* Jump to message. */
1942+
p += 2;
1943+
strbuf_addstr(&msg, "git stash: ");
1944+
strbuf_add(&msg, p, bufsize - (p - buffer));
1945+
1946+
author = xmemdupz(orig_author, author_len);
1947+
committer = xmemdupz(orig_committer, committer_len);
1948+
1949+
if (commit_tree_extended(msg.buf, msg.len,
1950+
r->hash_algo->empty_tree, parents,
1951+
out, author, committer,
1952+
NULL, NULL)) {
1953+
ret = error(_("could not write commit"));
1954+
goto out;
1955+
}
1956+
out:
1957+
strbuf_release(&msg);
1958+
repo_unuse_commit_buffer(r, this, buffer);
1959+
free_commit_list(parents);
1960+
free(author);
1961+
free(committer);
1962+
return ret;
1963+
}
1964+
1965+
static int do_export_stash(struct repository *r,
1966+
const char *ref,
1967+
int argc,
1968+
const char **argv)
1969+
{
1970+
struct object_id base;
1971+
struct object_context unused;
1972+
struct commit *prev;
1973+
struct oid_array items = OID_ARRAY_INIT;
1974+
int res = 0;
1975+
int i;
1976+
struct strbuf revision = STRBUF_INIT;
1977+
const char *author, *committer;
1978+
1979+
/*
1980+
* This is an arbitrary, fixed date, specifically the one used by git
1981+
* format-patch. The goal is merely to produce reproducible output.
1982+
*/
1983+
prepare_fallback_ident("git stash", "git@stash");
1984+
author = fmt_ident("git stash", "git@stash", WANT_BLANK_IDENT,
1985+
"2001-09-17T00:00:00Z", 0);
1986+
committer = fmt_ident("git stash", "git@stash", WANT_BLANK_IDENT,
1987+
"2001-09-17T00:00:00Z", 0);
1988+
1989+
/* First, we create a single empty commit. */
1990+
if (commit_tree_extended("", 0, r->hash_algo->empty_tree, NULL,
1991+
&base, author, committer, NULL, NULL))
1992+
return error(_("unable to write base commit"));
1993+
1994+
prev = lookup_commit_reference(r, &base);
1995+
1996+
if (argc) {
1997+
/*
1998+
* Find each specified stash, and load data into the array.
1999+
*/
2000+
for (i = 0; i < argc; i++) {
2001+
struct object_id oid;
2002+
if (parse_revision(&revision, argv[i], 1) ||
2003+
get_oid_with_context(r, revision.buf,
2004+
GET_OID_QUIETLY | GET_OID_GENTLY,
2005+
&oid, &unused)) {
2006+
res = error(_("unable to find stash entry %s"), argv[i]);
2007+
goto out;
2008+
}
2009+
oid_array_append(&items, &oid);
2010+
}
2011+
} else {
2012+
/*
2013+
* Walk the reflog, finding each stash entry, and load data into the
2014+
* array.
2015+
*/
2016+
for (i = 0;; i++) {
2017+
char buf[32];
2018+
struct object_id oid;
2019+
2020+
snprintf(buf, sizeof(buf), "%d", i);
2021+
if (parse_revision(&revision, buf, 1) ||
2022+
get_oid_with_context(r, revision.buf,
2023+
GET_OID_QUIETLY | GET_OID_GENTLY,
2024+
&oid, &unused))
2025+
break;
2026+
oid_array_append(&items, &oid);
2027+
}
2028+
}
2029+
2030+
/*
2031+
* Now, create a set of commits identical to the regular stash commits,
2032+
* but where their first parents form a chain to our original empty
2033+
* base commit.
2034+
*/
2035+
for (i = items.nr - 1; i >= 0; i--) {
2036+
struct commit_list *parents = NULL;
2037+
struct commit_list **next = &parents;
2038+
struct object_id out;
2039+
const struct object_id *oid = items.oid + i;
2040+
2041+
next = commit_list_append(prev, next);
2042+
next = commit_list_append(lookup_commit_reference(r, oid), next);
2043+
res = write_commit_with_parents(r, &out, oid, parents);
2044+
if (res)
2045+
goto out;
2046+
prev = lookup_commit_reference(r, &out);
2047+
}
2048+
if (ref)
2049+
refs_update_ref(get_main_ref_store(r), NULL, ref,
2050+
&prev->object.oid, NULL, 0, UPDATE_REFS_DIE_ON_ERR);
2051+
else
2052+
puts(oid_to_hex(&prev->object.oid));
2053+
out:
2054+
strbuf_release(&revision);
2055+
oid_array_clear(&items);
2056+
2057+
return res;
2058+
}
2059+
2060+
enum export_action {
2061+
ACTION_NONE,
2062+
ACTION_PRINT,
2063+
ACTION_TO_REF,
2064+
};
2065+
2066+
static int export_stash(int argc,
2067+
const char **argv,
2068+
const char *prefix,
2069+
struct repository *repo)
2070+
{
2071+
const char *ref = NULL;
2072+
enum export_action action = ACTION_NONE;
2073+
struct option options[] = {
2074+
OPT_CMDMODE(0, "print", &action,
2075+
N_("print the object ID instead of writing it to a ref"),
2076+
ACTION_PRINT),
2077+
OPT_CMDMODE(0, "to-ref", &action,
2078+
N_("save the data to the given ref"),
2079+
ACTION_TO_REF),
2080+
OPT_END()
2081+
};
2082+
2083+
argc = parse_options(argc, argv, prefix, options,
2084+
git_stash_export_usage,
2085+
PARSE_OPT_KEEP_DASHDASH);
2086+
2087+
if (action == ACTION_NONE) {
2088+
return error(_("exactly one of --print and --to-ref is required"));
2089+
} else if (action == ACTION_TO_REF) {
2090+
if (!argc)
2091+
return error(_("--to-ref requires an argument"));
2092+
ref = argv[0];
2093+
argc--;
2094+
argv++;
2095+
}
2096+
2097+
return do_export_stash(repo, ref, argc, argv);
2098+
}
2099+
18992100
int cmd_stash(int argc,
19002101
const char **argv,
19012102
const char *prefix,
@@ -1916,6 +2117,7 @@ int cmd_stash(int argc,
19162117
OPT_SUBCOMMAND("store", &fn, store_stash),
19172118
OPT_SUBCOMMAND("create", &fn, create_stash),
19182119
OPT_SUBCOMMAND("push", &fn, push_stash_unassumed),
2120+
OPT_SUBCOMMAND("export", &fn, export_stash),
19192121
OPT_SUBCOMMAND_F("save", &fn, save_stash, PARSE_OPT_NOCOMPLETE),
19202122
OPT_END()
19212123
};

0 commit comments

Comments
 (0)