Add support for history --merge to incorporate history changes from

other sessions.

Fixes #825
This commit is contained in:
ridiculousfish 2014-07-25 10:08:21 -07:00
parent 317660c2fe
commit e9f870e25a
5 changed files with 68 additions and 8 deletions

View file

@ -3512,6 +3512,7 @@ static int builtin_history(parser_t &parser, wchar_t **argv)
bool search_prefix = false;
bool save_history = false;
bool clear_history = false;
bool merge_history = false;
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"save", no_argument, 0, 'v' },
{ L"clear", no_argument, 0, 'l' },
{ L"merge", no_argument, 0, 'm' },
{ L"help", no_argument, 0, 'h' },
{ 0, 0, 0, 0 }
};
@ -3555,6 +3557,9 @@ static int builtin_history(parser_t &parser, wchar_t **argv)
case 'l':
clear_history = true;
break;
case 'm':
merge_history = true;
break;
case 'h':
builtin_print_help(parser, argv[0], stdout_buffer);
return STATUS_BUILTIN_OK;
@ -3584,6 +3589,11 @@ static int builtin_history(parser_t &parser, wchar_t **argv)
return STATUS_BUILTIN_OK;
}
if (merge_history)
{
history->incorporate_external_changes();
}
if (search_history)
{
int res = STATUS_BUILTIN_ERROR;

View file

@ -2,7 +2,7 @@
\subsection history-synopsis Synopsis
<pre>
history (--save | --clear)
history (--save | --clear | --merge)
history (--search | --delete ) (--prefix "prefix string" | --contains "search string")
</pre>
@ -16,6 +16,9 @@ The following options are available:
saves the history file; this option is provided for internal use.
- \c --clear clears the history file. A prompt is displayed before the history
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 --contains options.
- \c --delete deletes history items.

View file

@ -2688,7 +2688,8 @@ void history_tests_t::test_history_merge(void)
const size_t count = 3;
const wcstring name = L"merge_test";
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 */
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]));
}
/* 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 */
for (size_t i=0; i < 3; i++)
{

View file

@ -536,7 +536,7 @@ history_t::history_t(const wcstring &pname) :
mmap_start(NULL),
mmap_length(0),
mmap_file_id(kInvalidFileID),
birth_timestamp(time(NULL)),
boundary_timestamp(time(NULL)),
countdown_to_vacuum(-1),
loaded_old(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)
{
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++;
}
this->add(history_item_t(str, when, ident));
}
@ -1008,7 +1010,7 @@ void history_t::populate_from_mmap(void)
size_t cursor = 0;
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 (offset == (size_t)(-1))
break;
@ -1259,6 +1261,7 @@ static wcstring history_filename(const wcstring &name, const wcstring &suffix)
void history_t::clear_file_state()
{
ASSERT_IS_LOCKED(lock);
/* Erase everything we know about our file */
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()
{
}

View file

@ -154,8 +154,8 @@ private:
/** The file ID of the file we mmap'd */
file_id_t mmap_file_id;
/** Timestamp of when this history was created */
const time_t birth_timestamp;
/** 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() */
time_t boundary_timestamp;
/** How many items we add until the next vacuum. Initially a random value. */
int countdown_to_vacuum;
@ -233,6 +233,9 @@ public:
/** Populates from a bash history file */
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! */
void get_string_representation(wcstring &str, const wcstring &separator);