Skip to content
Snippets Groups Projects
Commit 95b35fac authored by Douwe Maan's avatar Douwe Maan
Browse files

Merge branch 'add-mkdir-support' into 'master'

Add functionality to create a directory with an empty .gitkeep file

This is in preparation to support gitlab-org/gitlab-ce#2557.

Also fixes an issue where file modes were overwritten after a commit: gitlab-org/gitlab-ce#2799.

Supports capability to reject updates of existing files: https://github.com/gitlabhq/gitlabhq/issues/8253

See merge request !49
parents dfc0fc2b ad5cf444
No related branches found
No related tags found
No related merge requests found
Pipeline #
v 7.2.18
- Support capability to reject updating of existing files (Stan Hu)
- Preserve file mode when updating existing file (Stan Hu)
- Add mkdir support with an empty .gitkeep file (Stan Hu)
v 7.2.17
- Ensure empty commits are returned by Commit.where (Rémy Coutable)
 
Loading
Loading
require_relative 'encoding_helper'
require_relative 'path_helper'
module Gitlab
module Git
class Blob
Loading
Loading
@@ -58,7 +61,7 @@ module Gitlab
def find_entry_by_path(repository, root_id, path)
root_tree = repository.lookup(root_id)
# Strip leading slashes
path.slice!(0) if path[0] == "/"
path[/^\/*/] = ''
path_arr = path.split('/')
 
entry = root_tree.find do |entry|
Loading
Loading
@@ -91,7 +94,8 @@ module Gitlab
# options should contain next structure:
# file: {
# content: 'Lorem ipsum...',
# path: 'documents/story.txt'
# path: 'documents/story.txt',
# update: true
# },
# author: {
# email: 'user@example.com',
Loading
Loading
@@ -110,6 +114,7 @@ module Gitlab
#
def commit(repository, options, action = :add)
file = options[:file]
update = file[:update].nil? ? true : file[:update]
author = options[:author]
committer = options[:committer]
commit = options[:commit]
Loading
Loading
@@ -121,17 +126,33 @@ module Gitlab
ref = 'refs/heads/' + ref
end
 
path_name = PathHelper.normalize_path(file[:path])
# Abort if any invalid characters remain (e.g. ../foo)
raise Repository::InvalidBlobName.new("Invalid path") if path_name.each_filename.to_a.include?('..')
filename = path_name.to_s
index = repo.index
 
unless repo.empty?
last_commit = repo.references[ref].target
rugged_ref = repo.references[ref]
raise Repository::InvalidRef.new("Invalid branch name") unless rugged_ref
last_commit = rugged_ref.target
index.read_tree(last_commit.tree)
parents = [last_commit]
end
 
if action == :remove
index.remove(file[:path])
index.remove(filename)
else
mode = 0100644
file_entry = index.get(filename)
if file_entry
raise Repository::InvalidBlobName.new("Filename already exists; update not allowed") unless update
# Preserve the current file mode if one is available
mode = file_entry[:mode] if file_entry[:mode]
end
content = file[:content]
detect = CharlockHolmes::EncodingDetector.new.detect(content) if content
 
Loading
Loading
@@ -142,7 +163,7 @@ module Gitlab
end
 
oid = repo.write(content, :blob)
index.add(path: file[:path], oid: oid, mode: 0100644)
index.add(path: filename, oid: oid, mode: mode)
end
 
opts = {}
Loading
Loading
module Gitlab
module Git
class PathHelper
class << self
def normalize_path(filename)
# Strip all leading slashes so that //foo -> foo
filename[/^\/*/] = ''
# Expand relative paths (e.g. foo/../bar)
filename = Pathname.new(filename)
filename.relative_path_from(Pathname.new(''))
end
end
end
end
end
# Gitlab::Git::Repository is a wrapper around native Rugged::Repository object
require_relative 'encoding_helper'
require_relative 'path_helper'
require 'tempfile'
require "rubygems/package"
 
Loading
Loading
@@ -12,6 +13,7 @@ module Gitlab
 
class NoRepository < StandardError; end
class InvalidBlobName < StandardError; end
class InvalidRef < StandardError; end
 
# Default branch in the repository
attr_accessor :root_ref
Loading
Loading
@@ -807,6 +809,53 @@ module Gitlab
rugged.config['core.autocrlf'] = AUTOCRLF_VALUES.invert[value]
end
 
# Create a new directory with a .gitkeep file. Creates
# all required nested directories (i.e. mkdir -p behavior)
#
# options should contain next structure:
# author: {
# email: 'user@example.com',
# name: 'Test User',
# time: Time.now
# },
# committer: {
# email: 'user@example.com',
# name: 'Test User',
# time: Time.now
# },
# commit: {
# message: 'Wow such commit',
# branch: 'master'
# }
def mkdir(path, options = {})
# Check if this directory exists; if it does, then don't bother
# adding .gitkeep file.
ref = options[:commit][:branch]
path = PathHelper.normalize_path(path).to_s
rugged_ref = rugged.ref(ref)
raise InvalidRef.new("Invalid ref") if rugged_ref.nil?
target_commit = rugged_ref.target
raise InvalidRef.new("Invalid target commit") if target_commit.nil?
entry = tree_entry(target_commit, path)
if entry
if entry[:type] == :blob
raise InvalidBlobName.new("Directory already exists as a file")
else
raise InvalidBlobName.new("Directory already exists")
end
end
options[:file] = {
content: '',
path: "#{path}/.gitkeep",
update: true
}
Blob.commit(self, options)
end
private
 
# Get the content of a blob for a given commit. If the blob is a commit
Loading
Loading
@@ -901,11 +950,15 @@ module Gitlab
# Find the entry for +path+ in the tree for +commit+
def tree_entry(commit, path)
pathname = Pathname.new(path)
first = true
tmp_entry = nil
 
pathname.each_filename do |dir|
if tmp_entry.nil?
if first
tmp_entry = commit.tree[dir]
first = false
elsif tmp_entry.nil?
return nil
else
tmp_entry = rugged.lookup(tmp_entry[:oid])
return nil unless tmp_entry.type == :tree
Loading
Loading
# encoding: UTF-8
# encoding: utf-8
 
require "spec_helper"
 
Loading
Loading
@@ -166,13 +166,13 @@ describe Gitlab::Git::Blob do
},
commit: {
message: 'Wow such commit',
branch: 'feature'
branch: 'fix-mode'
}
}
end
 
let!(:commit_sha) { Gitlab::Git::Blob.commit(repository, commit_options) }
let!(:commit) { repository.lookup(commit_sha) }
let(:commit_sha) { Gitlab::Git::Blob.commit(repository, commit_options) }
let(:commit) { repository.lookup(commit_sha) }
 
it 'should add file with commit' do
# Commit message valid
Loading
Loading
@@ -186,6 +186,24 @@ describe Gitlab::Git::Blob do
# File was created
repository.lookup(tree[:oid]).first[:name].should == 'story.txt'
end
describe 'reject updates' do
it 'should reject updates' do
commit_options[:file][:update] = false
commit_options[:file][:path] = 'files/executables/ls'
expect{ commit_sha }.to raise_error('Filename already exists; update not allowed')
end
end
describe 'file modes' do
it 'should preserve file modes with commit' do
commit_options[:file][:path] = 'files/executables/ls'
entry = Gitlab::Git::Blob::find_entry_by_path(repository, commit.tree.oid, commit_options[:file][:path])
expect(entry[:filemode]).to eq(0100755)
end
end
end
 
describe :remove do
Loading
Loading
Loading
Loading
@@ -661,4 +661,83 @@ describe Gitlab::Git::Repository do
expect(repository.branches).to eq([])
end
end
describe '#mkdir' do
let(:commit_options) do
{
author: {
email: 'user@example.com',
name: 'Test User',
time: Time.now
},
committer: {
email: 'user@example.com',
name: 'Test User',
time: Time.now
},
commit: {
message: 'Test message',
branch: 'refs/heads/fix',
}
}
end
def generate_diff_for_path(path)
"diff --git a/#{path}/.gitkeep b/#{path}/.gitkeep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/#{path}/.gitkeep\n"
end
shared_examples 'mkdir diff check' do |path, expected_path|
it 'creates a directory' do
result = repository.mkdir(path, commit_options)
expect(result).not_to eq(nil)
diff_text = repository.diff_text("#{result}~1", result)
expected = generate_diff_for_path(expected_path)
expect(diff_text).to eq(expected)
# Verify another mkdir doesn't create a directory that already exists
expect{ repository.mkdir(path, commit_options) }.to raise_error('Directory already exists')
end
end
describe 'creates a directory in root directory' do
it_should_behave_like 'mkdir diff check', 'new_dir', 'new_dir'
end
describe 'creates a directory in subdirectory' do
it_should_behave_like 'mkdir diff check', 'files/ruby/test', 'files/ruby/test'
end
describe 'creates a directory in subdirectory with a slash' do
it_should_behave_like 'mkdir diff check', '/files/ruby/test2', 'files/ruby/test2'
end
describe 'creates a directory in subdirectory with multiple slashes' do
it_should_behave_like 'mkdir diff check', '//files/ruby/test3', 'files/ruby/test3'
end
describe 'handles relative paths' do
it_should_behave_like 'mkdir diff check', 'files/ruby/../test_relative', 'files/test_relative'
end
describe 'creates nested directories' do
it_should_behave_like 'mkdir diff check', 'files/missing/test', 'files/missing/test'
end
it 'does not attempt to create a directory with invalid relative path' do
expect{ repository.mkdir('../files/missing/test', commit_options) }.to raise_error('Invalid path')
end
it 'does not attempt to overwrite a file' do
expect{ repository.mkdir('README.md', commit_options) }.to raise_error('Directory already exists as a file')
end
it 'does not attempt to overwrite a directory' do
expect{ repository.mkdir('files', commit_options) }.to raise_error('Directory already exists')
end
end
end
module SeedRepo
module Repo
HEAD = "master"
BRANCHES = ["feature", "fix", "fix-blob-path", "fix-existing-submodule-dir", "master"]
BRANCHES = ["feature", "fix", "fix-blob-path", "fix-existing-submodule-dir", "fix-mode", "master"]
TAGS = ["v1.0.0", 'v1.1.0', 'v1.2.0', 'v1.2.1']
end
end
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment