<commit_before>/*
 * Copyright (C) 2011 <NAME> <<EMAIL>>
 *
 * SPDX-License-Identifier: LGPL-2.0+
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 * Author: <NAME> <<EMAIL>>
 */

#include "config.h"

#include "ot-main.h"
#include "ot-builtins.h"
#include "ostree.h"
#include "otutil.h"
#include "parse-datetime.h"

static gboolean opt_no_prune;
static gboolean opt_static_deltas_only;
static gint opt_depth = -1;
static gboolean opt_refs_only;
static char *opt_delete_commit;
static char *opt_keep_younger_than;
static char **opt_retain_branch_depth;
static char **opt_only_branches;

/* ATTENTION:
 * Please remember to update the bash-completion script (bash/ostree) and
 * man page (man/ostree-prune.xml) when changing the option list.
 */

static GOptionEntry options[] = {
  { "no-prune", 0, 0, G_OPTION_ARG_NONE, &opt_no_prune, "Only display unreachable objects; don't delete", NULL },
  { "refs-only", 0, 0, G_OPTION_ARG_NONE, &opt_refs_only, "Only compute reachability via refs", NULL },
  { "depth", 0, 0, G_OPTION_ARG_INT, &opt_depth, "Only traverse DEPTH parents for each commit (default: -1=infinite)", "DEPTH" },
  { "delete-commit", 0, 0, G_OPTION_ARG_STRING, &opt_delete_commit, "Specify a commit to delete", "COMMIT" },
  { "keep-younger-than", 0, 0, G_OPTION_ARG_STRING, &opt_keep_younger_than, "Prune all commits older than the specified date", "DATE" },
  { "static-deltas-only", 0, 0, G_OPTION_ARG_NONE, &opt_static_deltas_only, "Change the behavior of delete-commit and keep-younger-than to prune only static deltas" },
  { "retain-branch-depth", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_retain_branch_depth, "Additionally retain BRANCH=DEPTH commits", "BRANCH=DEPTH" },
  { "only-branch", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_only_branches, "Only prune BRANCH (may be specified multiple times)", "BRANCH" },
  { NULL }
};

static gboolean
delete_commit (OstreeRepo *repo, const char *commit_to_delete, GCancellable *cancellable, GError **error)
{
  g_autoptr(GHashTable) refs = NULL;  /* (element-type utf8 utf8) */
  g_autoptr(GHashTable) collection_refs = NULL;  /* (element-type OstreeCollectionRef utf8) */

  /* Check refs which are not in a collection. */
  if (!ostree_repo_list_refs (repo, NULL, &refs, cancellable, error))
    return FALSE;

  GLNX_HASH_TABLE_FOREACH_KV(refs, const char *, ref, const char *, commit)
    {
      if (g_strcmp0 (commit_to_delete, commit) == 0)
        return glnx_throw (error, "Commit '%s' is referenced by '%s'", commit_to_delete, ref);
    }

  /* And check refs which *are* in a collection. */
  if (!ostree_repo_list_collection_refs (repo, NULL, &collection_refs,
                                         OSTREE_REPO_LIST_REFS_EXT_EXCLUDE_REMOTES,
                                         cancellable, error))
    return FALSE;

  GLNX_HASH_TABLE_FOREACH_KV (collection_refs, const OstreeCollectionRef*, ref,
                              const char *, commit)
    {
      if (g_strcmp0 (commit_to_delete, commit) == 0)
        return glnx_throw (error, "Commit '%s' is referenced by (%s, %s)",
                           commit_to_delete, ref->collection_id, ref->ref_name);
    }

  if (!ot_enable_tombstone_commits (repo, error))
    return FALSE;

  if (!ostree_repo_delete_object (repo, OSTREE_OBJECT_TYPE_COMMIT, commit_to_delete, cancellable, error))
    return FALSE;

  return TRUE;
}

static gboolean
traverse_keep_younger_than (OstreeRepo *repo, const char *checksum,
                            struct timespec *ts,
                            GHashTable *reachable,
                            GCancellable *cancellable, GError **error)
{
  g_autofree char *next_checksum = g_strdup (checksum);

  /* This is the first commit in our loop, which has a ref pointing to it. We
   * don't want to auto-prune it.
   */
  if (!ostree_repo_traverse_commit_union (repo, checksum, 0, reachable,
                                          cancellable, error))
    return FALSE;

  while (TRUE)
    {
      g_autoptr(GVariant) commit = NULL;
      if (!ostree_repo_load_variant_if_exists (repo, OSTREE_OBJECT_TYPE_COMMIT, next_checksum,
                                               &commit, error))
        return FALSE;
      if (!commit)
        break; /* This commit was pruned, so we're done */

      guint64 commit_timestamp = ostree_commit_get_timestamp (commit);
      /* Is this commit newer than our --keep-younger-than spec? */
      if (commit_timestamp >= ts->tv_sec)
        {
          /* It's newer, traverse it */
          if (!ostree_repo_traverse_commit_union (repo, next_checksum, 0, reachable,
                                                  cancellable, error))
            return FALSE;

          g_free (next_checksum);
          next_checksum = ostree_commit_get_parent (commit);
          if (next_checksum)
            g_clear_pointer (&commit, (GDestroyNotify)g_variant_unref);
          else
            break; /* No parent, we're done */
        }
      else
        break; /* It's older than our spec, we're done */
    }

  return TRUE;
}

gboolean
ostree_builtin_prune (int argc, char **argv, OstreeCommandInvocation *invocation, GCancellable *cancellable, GError **error)
{
  g_autoptr(GOptionContext) context = g_option_context_new ("");
  g_autoptr(OstreeRepo) repo = NULL;
  if (!ostree_option_context_parse (context, options, &argc, &argv, invocation, &repo, cancellable, error))
    return FALSE;

  if (!opt_no_prune && !ostree_ensure_repo_writable (repo, error))
    return FALSE;

  /* Special handling for explicit commit deletion here - we do this
   * first.
   */
  if (opt_delete_commit)
    {
      if (opt_no_prune)
        {
          ot_util_usage_error (context, "Cannot specify both --delete-commit and --no-prune", error);
          return FALSE;
        }
        if (opt_static_deltas_only)
          {
            if(!ostree_repo_prune_static_deltas (repo, opt_delete_commit, cancellable, error))
              return FALSE;
          }
        else if (!delete_commit (repo, opt_delete_commit, cancellable, error))
          return FALSE;
    }
  else
    {
      /* In the future we should make this useful, but for now let's
       * error out since what we were doing before was very misleading.
       * https://github.com/ostreedev/ostree/issues/1479
       */
      if (opt_static_deltas_only)
        return glnx_throw (error, "--static-deltas-only requires --delete-commit; see https://github.com/ostreedev/ostree/issues/1479");
    }

  OstreeRepoPruneFlags pruneflags = 0;
  if (opt_refs_only)
    pruneflags |= OSTREE_REPO_PRUNE_FLAGS_REFS_ONLY;
  if (opt_no_prune)
    pruneflags |= OSTREE_REPO_PRUNE_FLAGS_NO_PRUNE;

  /* If no newer more complex options are specified, drop down to the original
   * prune API - both to avoid code duplication, and to keep it run from the
   * test suite.
   */
  gint n_objects_total;
  gint n_objects_pruned;
  guint64 objsize_total;
  if (!(opt_retain_branch_depth || opt_keep_younger_than || opt_only_branches))
    {
      if (!ostree_repo_prune (repo, pruneflags, opt_depth,
                              &n_objects_total, &n_objects_pruned, &objsize_total,
                              cancellable, error))
        return FALSE;
    }
  else
    {
      g_autoptr(GHashTable) all_refs = NULL;
      g_autoptr(GHashTable) reachable = ostree_repo_traverse_new_reachable ();
      g_autoptr(GHashTable) retain_branch_depth = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
      struct timespec keep_younger_than_ts;
      GHashTableIter hash_iter;
      gpointer key, value;

      /* Otherwise, the default is --refs-only; we set this just as a note */
      opt_refs_only = TRUE;
<commit_msg>prune: [scan-build] Initialize a variable<commit_after>/*
 * Copyright (C) 2011 <NAME> <<EMAIL>>
 *
 * SPDX-License-Identifier: LGPL-2.0+
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 * Author: <NAME> <<EMAIL>>
 */

#include "config.h"

#include "ot-main.h"
#include "ot-builtins.h"
#include "ostree.h"
#include "otutil.h"
#include "parse-datetime.h"

static gboolean opt_no_prune;
static gboolean opt_static_deltas_only;
static gint opt_depth = -1;
static gboolean opt_refs_only;
static char *opt_delete_commit;
static char *opt_keep_younger_than;
static char **opt_retain_branch_depth;
static char **opt_only_branches;

/* ATTENTION:
 * Please remember to update the bash-completion script (bash/ostree) and
 * man page (man/ostree-prune.xml) when changing the option list.
 */

static GOptionEntry options[] = {
  { "no-prune", 0, 0, G_OPTION_ARG_NONE, &opt_no_prune, "Only display unreachable objects; don't delete", NULL },
  { "refs-only", 0, 0, G_OPTION_ARG_NONE, &opt_refs_only, "Only compute reachability via refs", NULL },
  { "depth", 0, 0, G_OPTION_ARG_INT, &opt_depth, "Only traverse DEPTH parents for each commit (default: -1=infinite)", "DEPTH" },
  { "delete-commit", 0, 0, G_OPTION_ARG_STRING, &opt_delete_commit, "Specify a commit to delete", "COMMIT" },
  { "keep-younger-than", 0, 0, G_OPTION_ARG_STRING, &opt_keep_younger_than, "Prune all commits older than the specified date", "DATE" },
  { "static-deltas-only", 0, 0, G_OPTION_ARG_NONE, &opt_static_deltas_only, "Change the behavior of delete-commit and keep-younger-than to prune only static deltas" },
  { "retain-branch-depth", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_retain_branch_depth, "Additionally retain BRANCH=DEPTH commits", "BRANCH=DEPTH" },
  { "only-branch", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_only_branches, "Only prune BRANCH (may be specified multiple times)", "BRANCH" },
  { NULL }
};

static gboolean
delete_commit (OstreeRepo *repo, const char *commit_to_delete, GCancellable *cancellable, GError **error)
{
  g_autoptr(GHashTable) refs = NULL;  /* (element-type utf8 utf8) */
  g_autoptr(GHashTable) collection_refs = NULL;  /* (element-type OstreeCollectionRef utf8) */

  /* Check refs which are not in a collection. */
  if (!ostree_repo_list_refs (repo, NULL, &refs, cancellable, error))
    return FALSE;

  GLNX_HASH_TABLE_FOREACH_KV(refs, const char *, ref, const char *, commit)
    {
      if (g_strcmp0 (commit_to_delete, commit) == 0)
        return glnx_throw (error, "Commit '%s' is referenced by '%s'", commit_to_delete, ref);
    }

  /* And check refs which *are* in a collection. */
  if (!ostree_repo_list_collection_refs (repo, NULL, &collection_refs,
                                         OSTREE_REPO_LIST_REFS_EXT_EXCLUDE_REMOTES,
                                         cancellable, error))
    return FALSE;

  GLNX_HASH_TABLE_FOREACH_KV (collection_refs, const OstreeCollectionRef*, ref,
                              const char *, commit)
    {
      if (g_strcmp0 (commit_to_delete, commit) == 0)
        return glnx_throw (error, "Commit '%s' is referenced by (%s, %s)",
                           commit_to_delete, ref->collection_id, ref->ref_name);
    }

  if (!ot_enable_tombstone_commits (repo, error))
    return FALSE;

  if (!ostree_repo_delete_object (repo, OSTREE_OBJECT_TYPE_COMMIT, commit_to_delete, cancellable, error))
    return FALSE;

  return TRUE;
}

static gboolean
traverse_keep_younger_than (OstreeRepo *repo, const char *checksum,
                            struct timespec *ts,
                            GHashTable *reachable,
                            GCancellable *cancellable, GError **error)
{
  g_autofree char *next_checksum = g_strdup (checksum);

  /* This is the first commit in our loop, which has a ref pointing to it. We
   * don't want to auto-prune it.
   */
  if (!ostree_repo_traverse_commit_union (repo, checksum, 0, reachable,
                                          cancellable, error))
    return FALSE;

  while (TRUE)
    {
      g_autoptr(GVariant) commit = NULL;
      if (!ostree_repo_load_variant_if_exists (repo, OSTREE_OBJECT_TYPE_COMMIT, next_checksum,
                                               &commit, error))
        return FALSE;
      if (!commit)
        break; /* This commit was pruned, so we're done */

      guint64 commit_timestamp = ostree_commit_get_timestamp (commit);
      /* Is this commit newer than our --keep-younger-than spec? */
      if (commit_timestamp >= ts->tv_sec)
        {
          /* It's newer, traverse it */
          if (!ostree_repo_traverse_commit_union (repo, next_checksum, 0, reachable,
                                                  cancellable, error))
            return FALSE;

          g_free (next_checksum);
          next_checksum = ostree_commit_get_parent (commit);
          if (next_checksum)
            g_clear_pointer (&commit, (GDestroyNotify)g_variant_unref);
          else
            break; /* No parent, we're done */
        }
      else
        break; /* It's older than our spec, we're done */
    }

  return TRUE;
}

gboolean
ostree_builtin_prune (int argc, char **argv, OstreeCommandInvocation *invocation, GCancellable *cancellable, GError **error)
{
  g_autoptr(GOptionContext) context = g_option_context_new ("");
  g_autoptr(OstreeRepo) repo = NULL;
  if (!ostree_option_context_parse (context, options, &argc, &argv, invocation, &repo, cancellable, error))
    return FALSE;

  if (!opt_no_prune && !ostree_ensure_repo_writable (repo, error))
    return FALSE;

  /* Special handling for explicit commit deletion here - we do this
   * first.
   */
  if (opt_delete_commit)
    {
      if (opt_no_prune)
        {
          ot_util_usage_error (context, "Cannot specify both --delete-commit and --no-prune", error);
          return FALSE;
        }
        if (opt_static_deltas_only)
          {
            if(!ostree_repo_prune_static_deltas (repo, opt_delete_commit, cancellable, error))
              return FALSE;
          }
        else if (!delete_commit (repo, opt_delete_commit, cancellable, error))
          return FALSE;
    }
  else
    {
      /* In the future we should make this useful, but for now let's
       * error out since what we were doing before was very misleading.
       * https://github.com/ostreedev/ostree/issues/1479
       */
      if (opt_static_deltas_only)
        return glnx_throw (error, "--static-deltas-only requires --delete-commit; see https://github.com/ostreedev/ostree/issues/1479");
    }

  OstreeRepoPruneFlags pruneflags = 0;
  if (opt_refs_only)
    pruneflags |= OSTREE_REPO_PRUNE_FLAGS_REFS_ONLY;
  if (opt_no_prune)
    pruneflags |= OSTREE_REPO_PRUNE_FLAGS_NO_PRUNE;

  /* If no newer more complex options are specified, drop down to the original
   * prune API - both to avoid code duplication, and to keep it run from the
   * test suite.
   */
  gint n_objects_total;
  gint n_objects_pruned;
  guint64 objsize_total;
  if (!(opt_retain_branch_depth || opt_keep_younger_than || opt_only_branches))
    {
      if (!ostree_repo_prune (repo, pruneflags, opt_depth,
                              &n_objects_total, &n_objects_pruned, &objsize_total,
                              cancellable, error))
        return FALSE;
    }
  else
    {
      g_autoptr(GHashTable) all_refs = NULL;
      g_autoptr(GHashTable) reachable = ostree_repo_traverse_new_reachable ();
      g_autoptr(GHashTable) retain_branch_depth = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
      struct timespec keep_younger_than_ts = {0, };
      GHashTableIter hash_iter;
      gpointer key, value;

      /* Otherwise, the default is --refs-only; we set this just as a note */
      opt_refs_only = TRUE;

{'fraction_non_alphanumeric': 0.09705042816365367, 'fraction_numerical': 0.007314462416745956, 'mean_word_length': 2.9822401136632726, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 23, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 2}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 20, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6620252', 'n_tokens_mistral': 5537, 'n_tokens_neox': 5354, 'n_words': 1531}