Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 100 additions & 0 deletions src/Models/RefName.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
using System;

namespace SourceGit.Models
{
/// <summary>
/// Validates branch, tag and remote names using the same rules that git itself enforces,
/// mirroring `git check-ref-format --allow-onelevel`. Rules are taken from the git-check-ref-format documentation.
/// Rule 2 (a refname must contain at least one slash) is intentionally waived, matching `--allow-onelevel`.
/// </summary>
public static class RefName
{
public static bool IsValidBranchName(string name)
{
if (string.IsNullOrEmpty(name))
return false;

if (name.StartsWith('-'))
return false;

if (string.Equals(name, "HEAD", StringComparison.Ordinal))
return false;

return IsValidRefName(name);
}

public static bool IsValidTagName(string name)
{
if (string.IsNullOrEmpty(name))
return false;

if (name.StartsWith('-'))
return false;

return IsValidRefName(name);
}

public static bool IsValidRemoteName(string name)
{
if (string.IsNullOrEmpty(name))
return false;

return IsValidRefName(name);
}

private static bool IsValidRefName(string name)
{
// Rule 9: cannot be the single character '@'.
if (name.Equals("@", StringComparison.Ordinal))
return false;

// Rule 6: cannot begin or end with '/', or contain consecutive slashes.
if (name[0] == '/' || name[^1] == '/' || name.Contains("//", StringComparison.Ordinal))
return false;

// Rule 7: cannot end with a dot.
if (name[^1] == '.')
return false;

// Rule 3: cannot contain two consecutive dots.
if (name.Contains("..", StringComparison.Ordinal))
return false;

// Rule 8: cannot contain the sequence '@{'.
if (name.Contains("@{", StringComparison.Ordinal))
return false;

// Rules 4, 5 & 10: no control chars, DEL, space, or ~ ^ : ? * [ \.
foreach (var ch in name)
{
if (ch is < ' ' or '\x7f')
return false;

switch (ch)
{
case ' ':
case '~':
case '^':
case ':':
case '?':
case '*':
case '[':
case '\\':
return false;
}
}

// Rule 1: no slash-separated component may begin with a dot or end with ".lock".
foreach (var component in name.Split('/'))
{
if (component[0] == '.')
return false;

if (component.EndsWith(".lock", StringComparison.Ordinal))
return false;
}

return true;
}
}
}
4 changes: 3 additions & 1 deletion src/ViewModels/AddRemote.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ namespace SourceGit.ViewModels
public class AddRemote : Popup
{
[Required(ErrorMessage = "Remote name is required!!!")]
[RegularExpression(@"^[\w\-\.]+$", ErrorMessage = "Bad remote name format!!!")]
[CustomValidation(typeof(AddRemote), nameof(ValidateRemoteName))]
public string Name
{
Expand Down Expand Up @@ -59,6 +58,9 @@ public static ValidationResult ValidateRemoteName(string name, ValidationContext
{
if (ctx.ObjectInstance is AddRemote add)
{
if (!Models.RefName.IsValidRemoteName(name))
return new ValidationResult("Bad remote name format!!!");

var exists = add._repo.Remotes.Find(x => x.Name == name);
if (exists != null)
return new ValidationResult("A remote with given name already exists!!!");
Expand Down
4 changes: 3 additions & 1 deletion src/ViewModels/CheckoutBranchFromStash.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ public Models.Stash Target
}

[Required(ErrorMessage = "Branch name is required!")]
[RegularExpression(@"^[\w\-/\.#\+]+$", ErrorMessage = "Bad branch name format!")]
[CustomValidation(typeof(CheckoutBranchFromStash), nameof(ValidateBranchName))]
public string BranchName
{
Expand All @@ -30,6 +29,9 @@ public static ValidationResult ValidateBranchName(string name, ValidationContext
{
if (ctx.ObjectInstance is CheckoutBranchFromStash caller)
{
if (!Models.RefName.IsValidBranchName(name))
return new ValidationResult("Bad branch name format!");

foreach (var b in caller._repo.Branches)
{
if (b.FriendlyName.Equals(name, StringComparison.Ordinal))
Expand Down
4 changes: 3 additions & 1 deletion src/ViewModels/CreateBranch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ namespace SourceGit.ViewModels
public class CreateBranch : Popup
{
[Required(ErrorMessage = "Branch name is required!")]
[RegularExpression(@"^[\w\-/\.#\+]+$", ErrorMessage = "Bad branch name format!")]
[CustomValidation(typeof(CreateBranch), nameof(ValidateBranchName))]
public string Name
{
Expand Down Expand Up @@ -115,6 +114,9 @@ public static ValidationResult ValidateBranchName(string name, ValidationContext
{
if (ctx.ObjectInstance is CreateBranch creator)
{
if (!Models.RefName.IsValidBranchName(name))
return new ValidationResult("Bad branch name format!");

if (!creator._allowOverwrite)
{
foreach (var b in creator._repo.Branches)
Expand Down
4 changes: 3 additions & 1 deletion src/ViewModels/CreateTag.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ public object BasedOn
}

[Required(ErrorMessage = "Tag name is required!")]
[RegularExpression(@"^(?!\.)(?!/)(?!.*\.$)(?!.*/$)(?!.*\.\.)[\w\-\+\./]+$", ErrorMessage = "Bad tag name format!")]
[CustomValidation(typeof(CreateTag), nameof(ValidateTagName))]
public string TagName
{
Expand Down Expand Up @@ -74,6 +73,9 @@ public static ValidationResult ValidateTagName(string name, ValidationContext ct
{
if (ctx.ObjectInstance is CreateTag creator)
{
if (!Models.RefName.IsValidTagName(name))
return new ValidationResult("Bad tag name format!");

var found = creator._repo.Tags.Find(x => x.Name == name);
if (found != null)
return new ValidationResult("A tag with same name already exists!");
Expand Down
4 changes: 3 additions & 1 deletion src/ViewModels/EditRemote.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ namespace SourceGit.ViewModels
public class EditRemote : Popup
{
[Required(ErrorMessage = "Remote name is required!!!")]
[RegularExpression(@"^[\w\-\.]+$", ErrorMessage = "Bad remote name format!!!")]
[CustomValidation(typeof(EditRemote), nameof(ValidateRemoteName))]
public string Name
{
Expand Down Expand Up @@ -60,6 +59,9 @@ public static ValidationResult ValidateRemoteName(string name, ValidationContext
{
if (ctx.ObjectInstance is EditRemote edit)
{
if (!Models.RefName.IsValidRemoteName(name))
return new ValidationResult("Bad remote name format!!!");

foreach (var remote in edit._repo.Remotes)
{
if (remote != edit._remote && name == remote.Name)
Expand Down
4 changes: 3 additions & 1 deletion src/ViewModels/GitFlowStart.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ public string Prefix
}

[Required(ErrorMessage = "Name is required!!!")]
[RegularExpression(@"^[\w\-/\.#]+$", ErrorMessage = "Bad branch name format!")]
[CustomValidation(typeof(GitFlowStart), nameof(ValidateBranchName))]
public string Name
{
Expand Down Expand Up @@ -67,6 +66,9 @@ public static ValidationResult ValidateBranchName(string name, ValidationContext
{
if (ctx.ObjectInstance is GitFlowStart starter)
{
if (!Models.RefName.IsValidBranchName($"{starter.Prefix}{name}"))
return new ValidationResult("Bad branch name format!");

var check = $"{starter.Prefix}{name}";
foreach (var b in starter._repo.Branches)
{
Expand Down
7 changes: 4 additions & 3 deletions src/ViewModels/InitGitFlow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ public partial class InitGitFlow : Popup
private static partial Regex REG_TAG_PREFIX();

[Required(ErrorMessage = "Production branch name is required!!!")]
[RegularExpression(@"^[\w\-/\.]+$", ErrorMessage = "Bad branch name format!")]
[CustomValidation(typeof(InitGitFlow), nameof(ValidateBaseBranch))]
public string Production
{
Expand All @@ -21,7 +20,6 @@ public string Production
}

[Required(ErrorMessage = "Development branch name is required!!!")]
[RegularExpression(@"^[\w\-/\.]+$", ErrorMessage = "Bad branch name format!")]
[CustomValidation(typeof(InitGitFlow), nameof(ValidateBaseBranch))]
public string Develop
{
Expand Down Expand Up @@ -81,10 +79,13 @@ public InitGitFlow(Repository repo)
_production = "main";
}

public static ValidationResult ValidateBaseBranch(string _, ValidationContext ctx)
public static ValidationResult ValidateBaseBranch(string name, ValidationContext ctx)
{
if (ctx.ObjectInstance is InitGitFlow initializer)
{
if (!Models.RefName.IsValidBranchName(name))
return new ValidationResult("Bad branch name format!");

if (initializer._production == initializer._develop)
return new ValidationResult("Develop branch has the same name with production branch!");
}
Expand Down
4 changes: 3 additions & 1 deletion src/ViewModels/RenameBranch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ public Models.Branch Target
}

[Required(ErrorMessage = "Branch name is required!!!")]
[RegularExpression(@"^[\w\-/\.#\+]+$", ErrorMessage = "Bad branch name format!")]
[CustomValidation(typeof(RenameBranch), nameof(ValidateBranchName))]
public string Name
{
Expand All @@ -31,6 +30,9 @@ public static ValidationResult ValidateBranchName(string name, ValidationContext
{
if (ctx.ObjectInstance is RenameBranch rename)
{
if (!Models.RefName.IsValidBranchName(name))
return new ValidationResult("Bad branch name format!");

foreach (var b in rename._repo.Branches)
{
if (b.IsLocal && b != rename.Target && b.Name.Equals(name, StringComparison.Ordinal))
Expand Down