502 lines
11 KiB
C
502 lines
11 KiB
C
/* ide-vcs-uri.c
|
|
*
|
|
* Copyright 2015-2019 Christian Hergert <christian@hergert.me>
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program 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 General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
*/
|
|
|
|
#define G_LOG_DOMAIN "ide-vcs-uri"
|
|
|
|
#include "config.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "ide-vcs-uri.h"
|
|
|
|
G_DEFINE_BOXED_TYPE (IdeVcsUri, ide_vcs_uri, ide_vcs_uri_ref, ide_vcs_uri_unref)
|
|
|
|
struct _IdeVcsUri
|
|
{
|
|
volatile gint ref_count;
|
|
|
|
/*
|
|
* If the URI string was created and has not been changed, we try extra
|
|
* hard to provide the same URI back from ide_vcs_uri_to_string(). This
|
|
* field is cleared any time any of the other fields are changed.
|
|
*/
|
|
gchar *non_destructive_uri;
|
|
|
|
gchar *scheme;
|
|
gchar *user;
|
|
gchar *host;
|
|
gchar *path;
|
|
guint port;
|
|
};
|
|
|
|
static inline void
|
|
ide_vcs_uri_set_dirty (IdeVcsUri *self)
|
|
{
|
|
g_clear_pointer (&self->non_destructive_uri, g_free);
|
|
}
|
|
|
|
static gboolean
|
|
ide_vcs_uri_validate (const IdeVcsUri *self)
|
|
{
|
|
g_assert (self != NULL);
|
|
|
|
if (g_strcmp0 (self->scheme, "file") == 0)
|
|
return ((self->path != NULL) &&
|
|
(self->port == 0) &&
|
|
(self->host == NULL) &&
|
|
(self->user == NULL));
|
|
|
|
if ((g_strcmp0 (self->scheme, "http") == 0) ||
|
|
(g_strcmp0 (self->scheme, "ssh") == 0) ||
|
|
(g_strcmp0 (self->scheme, "git") == 0) ||
|
|
(g_strcmp0 (self->scheme, "https") == 0) ||
|
|
(g_strcmp0 (self->scheme, "rsync") == 0))
|
|
return ((self->path != NULL) && (self->host != NULL));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
ide_vcs_uri_parse (IdeVcsUri *self,
|
|
const gchar *str)
|
|
{
|
|
static GRegex *regex1;
|
|
static GRegex *regex2;
|
|
static GRegex *regex3;
|
|
static gsize initialized;
|
|
GMatchInfo *match_info = NULL;
|
|
gboolean ret = FALSE;
|
|
|
|
if (g_once_init_enter (&initialized))
|
|
{
|
|
/* http://stackoverflow.com/questions/2514859/regular-expression-for-git-repository */
|
|
|
|
regex1 = g_regex_new ("file://(.*)", 0, 0, NULL);
|
|
g_assert (regex1);
|
|
|
|
regex2 = g_regex_new ("(\\w+://)(.+@)*([\\w\\d\\.]+)(:[\\d]+){0,1}/*(.*)", 0, 0, NULL);
|
|
g_assert (regex2);
|
|
|
|
regex3 = g_regex_new ("(.+@)*([\\w\\d\\.]+):(.*)", 0, 0, NULL);
|
|
g_assert (regex3);
|
|
|
|
g_once_init_leave (&initialized, TRUE);
|
|
}
|
|
|
|
if (str == NULL)
|
|
return FALSE;
|
|
|
|
/* check for local file:// style uris */
|
|
g_regex_match (regex1, str, 0, &match_info);
|
|
if (g_match_info_matches (match_info))
|
|
{
|
|
g_autofree gchar *path = NULL;
|
|
|
|
path = g_match_info_fetch (match_info, 1);
|
|
|
|
ide_vcs_uri_set_scheme (self, "file://");
|
|
ide_vcs_uri_set_user (self, NULL);
|
|
ide_vcs_uri_set_host (self, NULL);
|
|
ide_vcs_uri_set_port (self, 0);
|
|
ide_vcs_uri_set_path (self, path);
|
|
|
|
ret = TRUE;
|
|
}
|
|
g_clear_pointer (&match_info, g_match_info_free);
|
|
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* check for ssh:// style network uris */
|
|
g_regex_match (regex2, str, 0, &match_info);
|
|
if (g_match_info_matches (match_info))
|
|
{
|
|
g_autofree gchar *scheme = NULL;
|
|
g_autofree gchar *user = NULL;
|
|
g_autofree gchar *host = NULL;
|
|
g_autofree gchar *path = NULL;
|
|
g_autofree gchar *portstr = NULL;
|
|
gint start_pos;
|
|
gint end_pos;
|
|
guint port = 0;
|
|
|
|
scheme = g_match_info_fetch (match_info, 1);
|
|
user = g_match_info_fetch (match_info, 2);
|
|
host = g_match_info_fetch (match_info, 3);
|
|
portstr = g_match_info_fetch (match_info, 4);
|
|
path = g_match_info_fetch (match_info, 5);
|
|
|
|
g_match_info_fetch_pos (match_info, 5, &start_pos, &end_pos);
|
|
|
|
if (*path != '~' && (start_pos > 0) && str [start_pos-1] == '/')
|
|
{
|
|
gchar *tmp;
|
|
|
|
tmp = path;
|
|
path = g_strdup_printf ("/%s", path);
|
|
g_free (tmp);
|
|
}
|
|
|
|
if (!ide_str_empty0 (portstr) && g_ascii_isdigit (portstr [1]))
|
|
port = CLAMP (atoi (&portstr [1]), 1, G_MAXINT16);
|
|
|
|
ide_vcs_uri_set_scheme (self, scheme);
|
|
ide_vcs_uri_set_user (self, user);
|
|
ide_vcs_uri_set_host (self, host);
|
|
ide_vcs_uri_set_port (self, port);
|
|
ide_vcs_uri_set_path (self, path);
|
|
|
|
ret = TRUE;
|
|
}
|
|
g_clear_pointer (&match_info, g_match_info_free);
|
|
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* check for user@host style uris */
|
|
g_regex_match (regex3, str, 0, &match_info);
|
|
if (g_match_info_matches (match_info))
|
|
{
|
|
g_autofree gchar *user = NULL;
|
|
g_autofree gchar *host = NULL;
|
|
g_autofree gchar *path = NULL;
|
|
|
|
user = g_match_info_fetch (match_info, 1);
|
|
host = g_match_info_fetch (match_info, 2);
|
|
path = g_match_info_fetch (match_info, 3);
|
|
|
|
if (path && path[0] != '~' && path[0] != '/')
|
|
{
|
|
g_autofree gchar *tmp = path;
|
|
path = g_strdup_printf ("~/%s", tmp);
|
|
}
|
|
|
|
ide_vcs_uri_set_user (self, user);
|
|
ide_vcs_uri_set_host (self, host);
|
|
ide_vcs_uri_set_path (self, path);
|
|
ide_vcs_uri_set_scheme (self, "ssh://");
|
|
|
|
ret = TRUE;
|
|
}
|
|
g_clear_pointer (&match_info, g_match_info_free);
|
|
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* try to avoid some in-progress schemes */
|
|
if (strstr (str, "://"))
|
|
return FALSE;
|
|
|
|
ide_vcs_uri_set_scheme (self, "file://");
|
|
ide_vcs_uri_set_user (self, NULL);
|
|
ide_vcs_uri_set_host (self, NULL);
|
|
ide_vcs_uri_set_port (self, 0);
|
|
ide_vcs_uri_set_path (self, str);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
IdeVcsUri *
|
|
ide_vcs_uri_new (const gchar *uri)
|
|
{
|
|
IdeVcsUri *self;
|
|
|
|
self = g_slice_new0 (IdeVcsUri);
|
|
self->ref_count = 1;
|
|
|
|
if (ide_vcs_uri_parse (self, uri) && ide_vcs_uri_validate (self))
|
|
{
|
|
self->non_destructive_uri = g_strdup (uri);
|
|
return self;
|
|
}
|
|
|
|
ide_vcs_uri_unref (self);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
ide_vcs_uri_finalize (IdeVcsUri *self)
|
|
{
|
|
g_free (self->non_destructive_uri);
|
|
g_free (self->scheme);
|
|
g_free (self->user);
|
|
g_free (self->host);
|
|
g_free (self->path);
|
|
g_slice_free (IdeVcsUri, self);
|
|
}
|
|
|
|
IdeVcsUri *
|
|
ide_vcs_uri_ref (IdeVcsUri *self)
|
|
{
|
|
g_return_val_if_fail (self, NULL);
|
|
g_return_val_if_fail (self->ref_count > 0, NULL);
|
|
|
|
g_atomic_int_inc (&self->ref_count);
|
|
|
|
return self;
|
|
}
|
|
|
|
void
|
|
ide_vcs_uri_unref (IdeVcsUri *self)
|
|
{
|
|
g_return_if_fail (self);
|
|
g_return_if_fail (self->ref_count > 0);
|
|
|
|
if (g_atomic_int_dec_and_test (&self->ref_count))
|
|
ide_vcs_uri_finalize (self);
|
|
}
|
|
|
|
const gchar *
|
|
ide_vcs_uri_get_scheme (const IdeVcsUri *self)
|
|
{
|
|
g_return_val_if_fail (self, NULL);
|
|
|
|
return self->scheme;
|
|
}
|
|
|
|
const gchar *
|
|
ide_vcs_uri_get_user (const IdeVcsUri *self)
|
|
{
|
|
g_return_val_if_fail (self, NULL);
|
|
|
|
return self->user;
|
|
}
|
|
|
|
const gchar *
|
|
ide_vcs_uri_get_host (const IdeVcsUri *self)
|
|
{
|
|
g_return_val_if_fail (self, NULL);
|
|
|
|
return self->host;
|
|
}
|
|
|
|
guint
|
|
ide_vcs_uri_get_port (const IdeVcsUri *self)
|
|
{
|
|
g_return_val_if_fail (self, 0);
|
|
|
|
return self->port;
|
|
}
|
|
|
|
const gchar *
|
|
ide_vcs_uri_get_path (const IdeVcsUri *self)
|
|
{
|
|
g_return_val_if_fail (self, NULL);
|
|
|
|
return self->path;
|
|
}
|
|
|
|
void
|
|
ide_vcs_uri_set_scheme (IdeVcsUri *self,
|
|
const gchar *scheme)
|
|
{
|
|
g_return_if_fail (self);
|
|
|
|
if (ide_str_empty0 (scheme))
|
|
scheme = NULL;
|
|
|
|
if (scheme != self->scheme)
|
|
{
|
|
const gchar *tmp;
|
|
|
|
g_clear_pointer (&self->scheme, g_free);
|
|
|
|
if (scheme != NULL && (tmp = strchr (scheme, ':')))
|
|
self->scheme = g_strndup (scheme, tmp - scheme);
|
|
else
|
|
self->scheme = g_strdup (scheme);
|
|
}
|
|
|
|
ide_vcs_uri_set_dirty (self);
|
|
}
|
|
|
|
void
|
|
ide_vcs_uri_set_user (IdeVcsUri *self,
|
|
const gchar *user)
|
|
{
|
|
g_return_if_fail (self);
|
|
|
|
if (ide_str_empty0 (user))
|
|
user = NULL;
|
|
|
|
if (user != self->user)
|
|
{
|
|
const gchar *tmp;
|
|
|
|
g_clear_pointer (&self->user, g_free);
|
|
|
|
if (user != NULL && (tmp = strchr (user, '@')))
|
|
self->user = g_strndup (user, tmp - user);
|
|
else
|
|
self->user = g_strdup (user);
|
|
}
|
|
|
|
ide_vcs_uri_set_dirty (self);
|
|
}
|
|
|
|
void
|
|
ide_vcs_uri_set_host (IdeVcsUri *self,
|
|
const gchar *host)
|
|
{
|
|
g_return_if_fail (self);
|
|
|
|
if (ide_str_empty0 (host))
|
|
host = NULL;
|
|
|
|
if (host != self->host)
|
|
{
|
|
g_free (self->host);
|
|
self->host = g_strdup (host);
|
|
}
|
|
|
|
ide_vcs_uri_set_dirty (self);
|
|
}
|
|
|
|
void
|
|
ide_vcs_uri_set_port (IdeVcsUri *self,
|
|
guint port)
|
|
{
|
|
g_return_if_fail (self);
|
|
g_return_if_fail (port <= G_MAXINT16);
|
|
|
|
self->port = port;
|
|
|
|
ide_vcs_uri_set_dirty (self);
|
|
}
|
|
|
|
void
|
|
ide_vcs_uri_set_path (IdeVcsUri *self,
|
|
const gchar *path)
|
|
{
|
|
g_return_if_fail (self);
|
|
|
|
if (ide_str_empty0 (path))
|
|
path = NULL;
|
|
|
|
if (path != self->path)
|
|
{
|
|
if (path != NULL && (*path == ':'))
|
|
path++;
|
|
g_free (self->path);
|
|
self->path = g_strdup (path);
|
|
}
|
|
|
|
ide_vcs_uri_set_dirty (self);
|
|
}
|
|
|
|
gchar *
|
|
ide_vcs_uri_to_string (const IdeVcsUri *self)
|
|
{
|
|
GString *str;
|
|
|
|
g_return_val_if_fail (self, NULL);
|
|
|
|
if (self->non_destructive_uri != NULL)
|
|
return g_strdup (self->non_destructive_uri);
|
|
|
|
str = g_string_new (NULL);
|
|
|
|
g_string_append_printf (str, "%s://", self->scheme);
|
|
|
|
if (0 == g_strcmp0 (self->scheme, "file"))
|
|
{
|
|
g_string_append (str, self->path);
|
|
return g_string_free (str, FALSE);
|
|
}
|
|
|
|
if (self->user != NULL)
|
|
g_string_append_printf (str, "%s@", self->user);
|
|
|
|
g_string_append (str, self->host);
|
|
|
|
if (self->port != 0)
|
|
g_string_append_printf (str, ":%u", self->port);
|
|
|
|
if (self->path == NULL)
|
|
g_string_append (str, "/");
|
|
else if (self->path [0] == '~')
|
|
g_string_append_printf (str, "/%s", self->path);
|
|
else if (self->path [0] != '/')
|
|
g_string_append_printf (str, "/%s", self->path);
|
|
else
|
|
g_string_append (str, self->path);
|
|
|
|
return g_string_free (str, FALSE);
|
|
}
|
|
|
|
gboolean
|
|
ide_vcs_uri_is_valid (const gchar *uri_string)
|
|
{
|
|
gboolean ret = FALSE;
|
|
|
|
if (uri_string != NULL)
|
|
{
|
|
IdeVcsUri *uri;
|
|
|
|
uri = ide_vcs_uri_new (uri_string);
|
|
ret = !!uri;
|
|
g_clear_pointer (&uri, ide_vcs_uri_unref);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* ide_vcs_uri_get_clone_name:
|
|
* @self: an #ideVcsUri
|
|
*
|
|
* Determines a suggested name for the checkout directory. Some special
|
|
* handling of suffixes such as ".git" are performed to improve the the
|
|
* quality of results.
|
|
*
|
|
* Returns: (transfer full) (nullable): a string containing the suggested
|
|
* clone directory name, or %NULL.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
gchar *
|
|
ide_vcs_uri_get_clone_name (const IdeVcsUri *self)
|
|
{
|
|
g_autofree gchar *name = NULL;
|
|
const gchar *path;
|
|
|
|
g_return_val_if_fail (self != NULL, NULL);
|
|
|
|
if (!(path = ide_vcs_uri_get_path (self)))
|
|
return NULL;
|
|
|
|
if (ide_str_empty0 (path))
|
|
return NULL;
|
|
|
|
if (!(name = g_path_get_basename (path)))
|
|
return NULL;
|
|
|
|
/* Trim trailing ".git" */
|
|
if (g_str_has_suffix (name, ".git"))
|
|
*(strrchr (name, '.')) = '\0';
|
|
|
|
if (!g_str_equal (name, "/") && !g_str_equal (name, "~"))
|
|
return g_steal_pointer (&name);
|
|
|
|
return NULL;
|
|
}
|