mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-26 21:03:12 +00:00
Add support for history --merge to incorporate history changes from
other sessions. Fixes #825
This commit is contained in:
parent
317660c2fe
commit
e9f870e25a
5 changed files with 68 additions and 8 deletions
10
builtin.cpp
10
builtin.cpp
|
@ -3512,6 +3512,7 @@ static int builtin_history(parser_t &parser, wchar_t **argv)
|
||||||
bool search_prefix = false;
|
bool search_prefix = false;
|
||||||
bool save_history = false;
|
bool save_history = false;
|
||||||
bool clear_history = false;
|
bool clear_history = false;
|
||||||
|
bool merge_history = false;
|
||||||
|
|
||||||
static const struct woption long_options[] =
|
static const struct woption long_options[] =
|
||||||
{
|
{
|
||||||
|
@ -3521,6 +3522,7 @@ static int builtin_history(parser_t &parser, wchar_t **argv)
|
||||||
{ L"contains", no_argument, 0, 'c' },
|
{ L"contains", no_argument, 0, 'c' },
|
||||||
{ L"save", no_argument, 0, 'v' },
|
{ L"save", no_argument, 0, 'v' },
|
||||||
{ L"clear", no_argument, 0, 'l' },
|
{ L"clear", no_argument, 0, 'l' },
|
||||||
|
{ L"merge", no_argument, 0, 'm' },
|
||||||
{ L"help", no_argument, 0, 'h' },
|
{ L"help", no_argument, 0, 'h' },
|
||||||
{ 0, 0, 0, 0 }
|
{ 0, 0, 0, 0 }
|
||||||
};
|
};
|
||||||
|
@ -3555,6 +3557,9 @@ static int builtin_history(parser_t &parser, wchar_t **argv)
|
||||||
case 'l':
|
case 'l':
|
||||||
clear_history = true;
|
clear_history = true;
|
||||||
break;
|
break;
|
||||||
|
case 'm':
|
||||||
|
merge_history = true;
|
||||||
|
break;
|
||||||
case 'h':
|
case 'h':
|
||||||
builtin_print_help(parser, argv[0], stdout_buffer);
|
builtin_print_help(parser, argv[0], stdout_buffer);
|
||||||
return STATUS_BUILTIN_OK;
|
return STATUS_BUILTIN_OK;
|
||||||
|
@ -3584,6 +3589,11 @@ static int builtin_history(parser_t &parser, wchar_t **argv)
|
||||||
return STATUS_BUILTIN_OK;
|
return STATUS_BUILTIN_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (merge_history)
|
||||||
|
{
|
||||||
|
history->incorporate_external_changes();
|
||||||
|
}
|
||||||
|
|
||||||
if (search_history)
|
if (search_history)
|
||||||
{
|
{
|
||||||
int res = STATUS_BUILTIN_ERROR;
|
int res = STATUS_BUILTIN_ERROR;
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
\subsection history-synopsis Synopsis
|
\subsection history-synopsis Synopsis
|
||||||
<pre>
|
<pre>
|
||||||
history (--save | --clear)
|
history (--save | --clear | --merge)
|
||||||
history (--search | --delete ) (--prefix "prefix string" | --contains "search string")
|
history (--search | --delete ) (--prefix "prefix string" | --contains "search string")
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
|
@ -16,6 +16,9 @@ The following options are available:
|
||||||
saves the history file; this option is provided for internal use.
|
saves the history file; this option is provided for internal use.
|
||||||
- \c --clear clears the history file. A prompt is displayed before the history
|
- \c --clear clears the history file. A prompt is displayed before the history
|
||||||
is erased.
|
is erased.
|
||||||
|
- \c --merge immediately incorporates history changes from other sessions. Ordinarily
|
||||||
|
fish ignores history changes from sessions started after the current one. This command
|
||||||
|
applies those changes immediately.
|
||||||
- \c --search returns history items in keeping with the \c --prefix or
|
- \c --search returns history items in keeping with the \c --prefix or
|
||||||
\c --contains options.
|
\c --contains options.
|
||||||
- \c --delete deletes history items.
|
- \c --delete deletes history items.
|
||||||
|
|
|
@ -2688,7 +2688,8 @@ void history_tests_t::test_history_merge(void)
|
||||||
const size_t count = 3;
|
const size_t count = 3;
|
||||||
const wcstring name = L"merge_test";
|
const wcstring name = L"merge_test";
|
||||||
history_t *hists[count] = {new history_t(name), new history_t(name), new history_t(name)};
|
history_t *hists[count] = {new history_t(name), new history_t(name), new history_t(name)};
|
||||||
wcstring texts[count] = {L"History 1", L"History 2", L"History 3"};
|
const wcstring texts[count] = {L"History 1", L"History 2", L"History 3"};
|
||||||
|
const wcstring alt_texts[count] = {L"History Alt 1", L"History Alt 2", L"History Alt 3"};
|
||||||
|
|
||||||
/* Make sure history is clear */
|
/* Make sure history is clear */
|
||||||
for (size_t i=0; i < count; i++)
|
for (size_t i=0; i < count; i++)
|
||||||
|
@ -2730,6 +2731,32 @@ void history_tests_t::test_history_merge(void)
|
||||||
do_test(history_contains(everything, texts[i]));
|
do_test(history_contains(everything, texts[i]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Tell all histories to merge. Now everybody should have everything. */
|
||||||
|
for (size_t i=0; i < count; i++)
|
||||||
|
{
|
||||||
|
hists[i]->incorporate_external_changes();
|
||||||
|
}
|
||||||
|
/* Add some more per-history items */
|
||||||
|
for (size_t i=0; i < count; i++)
|
||||||
|
{
|
||||||
|
hists[i]->add(alt_texts[i]);
|
||||||
|
}
|
||||||
|
/* Everybody should have old items, but only one history should have each new item */
|
||||||
|
for (size_t i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
for (size_t j=0; j < count; j++)
|
||||||
|
{
|
||||||
|
/* Old item */
|
||||||
|
do_test(history_contains(hists[i], texts[j]));
|
||||||
|
|
||||||
|
/* New item */
|
||||||
|
bool does_contain = history_contains(hists[i], alt_texts[j]);
|
||||||
|
bool should_contain = (i == j);
|
||||||
|
do_test(should_contain == does_contain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Clean up */
|
/* Clean up */
|
||||||
for (size_t i=0; i < 3; i++)
|
for (size_t i=0; i < 3; i++)
|
||||||
{
|
{
|
||||||
|
|
25
history.cpp
25
history.cpp
|
@ -536,7 +536,7 @@ history_t::history_t(const wcstring &pname) :
|
||||||
mmap_start(NULL),
|
mmap_start(NULL),
|
||||||
mmap_length(0),
|
mmap_length(0),
|
||||||
mmap_file_id(kInvalidFileID),
|
mmap_file_id(kInvalidFileID),
|
||||||
birth_timestamp(time(NULL)),
|
boundary_timestamp(time(NULL)),
|
||||||
countdown_to_vacuum(-1),
|
countdown_to_vacuum(-1),
|
||||||
loaded_old(false),
|
loaded_old(false),
|
||||||
chaos_mode(false)
|
chaos_mode(false)
|
||||||
|
@ -606,10 +606,12 @@ void history_t::save_internal_unless_disabled()
|
||||||
void history_t::add(const wcstring &str, history_identifier_t ident)
|
void history_t::add(const wcstring &str, history_identifier_t ident)
|
||||||
{
|
{
|
||||||
time_t when = time(NULL);
|
time_t when = time(NULL);
|
||||||
/* Big hack: do not allow timestamps equal to our birthdate. This is because we include items whose timestamps are equal to our birthdate when reading old history, so we can catch "just closed" items. But this means that we may interpret our own items, that we just wrote, as old items, if we wrote them in the same second as our birthdate.
|
/* Big hack: do not allow timestamps equal to our boundary date. This is because we include items whose timestamps are equal to our boundary when reading old history, so we can catch "just closed" items. But this means that we may interpret our own items, that we just wrote, as old items, if we wrote them in the same second as our birthdate.
|
||||||
*/
|
*/
|
||||||
if (when == this->birth_timestamp)
|
if (when == this->boundary_timestamp)
|
||||||
|
{
|
||||||
when++;
|
when++;
|
||||||
|
}
|
||||||
|
|
||||||
this->add(history_item_t(str, when, ident));
|
this->add(history_item_t(str, when, ident));
|
||||||
}
|
}
|
||||||
|
@ -1008,7 +1010,7 @@ void history_t::populate_from_mmap(void)
|
||||||
size_t cursor = 0;
|
size_t cursor = 0;
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
size_t offset = offset_of_next_item(mmap_start, mmap_length, mmap_type, &cursor, birth_timestamp);
|
size_t offset = offset_of_next_item(mmap_start, mmap_length, mmap_type, &cursor, boundary_timestamp);
|
||||||
// If we get back -1, we're done
|
// If we get back -1, we're done
|
||||||
if (offset == (size_t)(-1))
|
if (offset == (size_t)(-1))
|
||||||
break;
|
break;
|
||||||
|
@ -1259,6 +1261,7 @@ static wcstring history_filename(const wcstring &name, const wcstring &suffix)
|
||||||
|
|
||||||
void history_t::clear_file_state()
|
void history_t::clear_file_state()
|
||||||
{
|
{
|
||||||
|
ASSERT_IS_LOCKED(lock);
|
||||||
/* Erase everything we know about our file */
|
/* Erase everything we know about our file */
|
||||||
if (mmap_start != NULL && mmap_start != MAP_FAILED)
|
if (mmap_start != NULL && mmap_start != MAP_FAILED)
|
||||||
{
|
{
|
||||||
|
@ -1692,6 +1695,20 @@ void history_t::populate_from_bash(FILE *stream)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void history_t::incorporate_external_changes()
|
||||||
|
{
|
||||||
|
/* To incorporate new items, we simply update our timestamp to now, so that items from previous instances get added. We then clear the file state so that we remap the file. Note that this is somehwhat expensive because we will be going back over old items. An optimization would be to preserve old_item_offsets so that they don't have to be recomputed. (However, then items *deleted* in other instances would not show up here). */
|
||||||
|
time_t new_timestamp = time(NULL);
|
||||||
|
scoped_lock locker(lock);
|
||||||
|
|
||||||
|
/* If for some reason the clock went backwards, we don't want to start dropping items; therefore we only do work if time has progressed. This also makes multiple calls cheap. */
|
||||||
|
if (new_timestamp > this->boundary_timestamp)
|
||||||
|
{
|
||||||
|
this->boundary_timestamp = new_timestamp;
|
||||||
|
this->clear_file_state();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void history_init()
|
void history_init()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
@ -154,8 +154,8 @@ private:
|
||||||
/** The file ID of the file we mmap'd */
|
/** The file ID of the file we mmap'd */
|
||||||
file_id_t mmap_file_id;
|
file_id_t mmap_file_id;
|
||||||
|
|
||||||
/** Timestamp of when this history was created */
|
/** The boundary timestamp distinguishes old items from new items. Items whose timestamps are <= the boundary are considered "old". Items whose timestemps are > the boundary are new, and are ignored by this instance (unless they came from this instance). The timestamp may be adjusted by incorporate_external_changes() */
|
||||||
const time_t birth_timestamp;
|
time_t boundary_timestamp;
|
||||||
|
|
||||||
/** How many items we add until the next vacuum. Initially a random value. */
|
/** How many items we add until the next vacuum. Initially a random value. */
|
||||||
int countdown_to_vacuum;
|
int countdown_to_vacuum;
|
||||||
|
@ -233,6 +233,9 @@ public:
|
||||||
/** Populates from a bash history file */
|
/** Populates from a bash history file */
|
||||||
void populate_from_bash(FILE *f);
|
void populate_from_bash(FILE *f);
|
||||||
|
|
||||||
|
/** Incorporates the history of other shells into this history */
|
||||||
|
void incorporate_external_changes();
|
||||||
|
|
||||||
/* Gets all the history into a string with ARRAY_SEP_STR. This is intended for the $history environment variable. This may be long! */
|
/* Gets all the history into a string with ARRAY_SEP_STR. This is intended for the $history environment variable. This may be long! */
|
||||||
void get_string_representation(wcstring &str, const wcstring &separator);
|
void get_string_representation(wcstring &str, const wcstring &separator);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue