Class: Gitlab::Git::Repository

Inherits:
Object
  • Object
show all
Includes:
EncodingHelper, RepositoryMirroring, WrapsGitalyErrors, Utils::StrongMemoize
Defined in:
lib/gitlab/git/repository.rb

Defined Under Namespace

Classes: CreateTreeError

Constant Summary collapse

SEARCH_CONTEXT_LINES =
3
REV_LIST_COMMIT_LIMIT =
2_000
GITALY_INTERNAL_URL =
'ssh://gitaly/internal.git'
GITLAB_PROJECTS_TIMEOUT =
Gitlab.config.gitlab_shell.git_timeout
EMPTY_REPOSITORY_CHECKSUM =
'0000000000000000000000000000000000000000'
NoRepository =
Class.new(StandardError)
InvalidRepository =
Class.new(StandardError)
InvalidBlobName =
Class.new(StandardError)
InvalidRef =
Class.new(StandardError)
GitError =
Class.new(StandardError)
DeleteBranchError =
Class.new(StandardError)
TagExistsError =
Class.new(StandardError)
ChecksumError =
Class.new(StandardError)

Constants included from EncodingHelper

EncodingHelper::ENCODING_CONFIDENCE_THRESHOLD

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Utils::StrongMemoize

#clear_memoization, #strong_memoize, #strong_memoized?

Methods included from EncodingHelper

#binary_io, #detect_binary?, #detect_libgit2_binary?, #encode!, #encode_binary, #encode_utf8

Methods included from WrapsGitalyErrors

#wrapped_gitaly_errors

Methods included from RepositoryMirroring

#remote_branches

Constructor Details

#initialize(storage, relative_path, gl_repository, gl_project_path) ⇒ Repository

This initializer method is only used on the client side (gitlab-ce). Gitaly-ruby uses a different initializer.


58
59
60
61
62
63
64
65
# File 'lib/gitlab/git/repository.rb', line 58

def initialize(storage, relative_path, gl_repository, gl_project_path)
  @storage = storage
  @relative_path = relative_path
  @gl_repository = gl_repository
  @gl_project_path = gl_project_path

  @name = @relative_path.split("/").last
end

Instance Attribute Details

#gl_project_pathObject (readonly)

Returns the value of attribute gl_project_path


47
48
49
# File 'lib/gitlab/git/repository.rb', line 47

def gl_project_path
  @gl_project_path
end

#gl_repositoryObject (readonly) Also known as: object_pool_remote_name

Returns the value of attribute gl_repository


47
48
49
# File 'lib/gitlab/git/repository.rb', line 47

def gl_repository
  @gl_repository
end

#nameObject (readonly)

Directory name of repo


42
43
44
# File 'lib/gitlab/git/repository.rb', line 42

def name
  @name
end

#relative_pathObject (readonly)

Relative path of repo


45
46
47
# File 'lib/gitlab/git/repository.rb', line 45

def relative_path
  @relative_path
end

#storageObject (readonly)

Returns the value of attribute storage


47
48
49
# File 'lib/gitlab/git/repository.rb', line 47

def storage
  @storage
end

Instance Method Details

#==(other) ⇒ Object Also known as: eql?


71
72
73
# File 'lib/gitlab/git/repository.rb', line 71

def ==(other)
  other.is_a?(self.class) && [storage, relative_path] == [other.storage, other.relative_path]
end

#add_branch(branch_name, user:, target:) ⇒ Object


549
550
551
552
553
# File 'lib/gitlab/git/repository.rb', line 549

def add_branch(branch_name, user:, target:)
  wrapped_gitaly_errors do
    gitaly_operation_client.user_create_branch(branch_name, user, target)
  end
end

#add_remote(remote_name, url, mirror_refmap: nil) ⇒ Object

If `mirror_refmap` is present the remote is set as mirror with that mapping


672
673
674
675
676
# File 'lib/gitlab/git/repository.rb', line 672

def add_remote(remote_name, url, mirror_refmap: nil)
  wrapped_gitaly_errors do
    gitaly_remote_client.add_remote(remote_name, url, mirror_refmap)
  end
end

#add_tag(tag_name, user:, target:, message: nil) ⇒ Object


555
556
557
558
559
# File 'lib/gitlab/git/repository.rb', line 555

def add_tag(tag_name, user:, target:, message: nil)
  wrapped_gitaly_errors do
    gitaly_operation_client.add_tag(tag_name, user, target, message)
  end
end

#ancestor?(from, to) ⇒ Boolean

Returns true is from is direct ancestor to to, otherwise false

Returns:

  • (Boolean)

421
422
423
# File 'lib/gitlab/git/repository.rb', line 421

def ancestor?(from, to)
  gitaly_commit_client.ancestor?(from, to)
end

#archive_metadata(ref, storage_path, project_path, format = "tar.gz", append_sha:, path: nil) ⇒ Object


238
239
240
241
242
243
244
245
246
247
248
249
250
251
# File 'lib/gitlab/git/repository.rb', line 238

def (ref, storage_path, project_path, format = "tar.gz", append_sha:, path: nil)
  ref ||= root_ref
  commit = Gitlab::Git::Commit.find(self, ref)
  return {} if commit.nil?

  prefix = archive_prefix(ref, commit.id, project_path, append_sha: append_sha, path: path)

  {
    'ArchivePrefix' => prefix,
    'ArchivePath' => archive_file_path(storage_path, commit.id, prefix, format),
    'CommitId' => commit.id,
    'GitalyRepository' => gitaly_repository.to_h
  }
end

#attributes(path) ⇒ Object

Returns the Git attributes for the given file path.

See `Gitlab::Git::Attributes` for more information.


717
718
719
# File 'lib/gitlab/git/repository.rb', line 717

def attributes(path)
  info_attributes.attributes(path)
end

#attributes_at(ref) ⇒ Object

Returns parsed .gitattributes for a given ref

This only parses the root .gitattributes file, it does not traverse subfolders to find additional .gitattributes files

This method is around 30 times slower than `attributes`, which uses `$GIT_DIR/info/attributes`. Consider caching AttributesAtRefParser and reusing that for multiple calls instead of this method.


733
734
735
# File 'lib/gitlab/git/repository.rb', line 733

def attributes_at(ref)
  AttributesAtRefParser.new(self, ref)
end

#batch_blobs(items, blob_size_limit: Gitlab::Git::Blob::MAX_DATA_DISPLAY_SIZE) ⇒ Object

Items should be of format [[commit_id, path], [commit_id1, path1]]


811
812
813
# File 'lib/gitlab/git/repository.rb', line 811

def batch_blobs(items, blob_size_limit: Gitlab::Git::Blob::MAX_DATA_DISPLAY_SIZE)
  Gitlab::Git::Blob.batch(self, items, blob_size_limit: blob_size_limit)
end

#blob_at(sha, path) ⇒ Object


806
807
808
# File 'lib/gitlab/git/repository.rb', line 806

def blob_at(sha, path)
  Gitlab::Git::Blob.find(self, sha, path) unless Gitlab::Git.blank_ref?(sha)
end

#branch_countObject

Returns the number of valid branches


137
138
139
140
141
# File 'lib/gitlab/git/repository.rb', line 137

def branch_count
  wrapped_gitaly_errors do
    gitaly_ref_client.count_branch_names
  end
end

#branch_exists?(name) ⇒ Boolean

Returns true if the given branch exists

name - The name of the branch as a String.

Returns:

  • (Boolean)

221
222
223
224
225
# File 'lib/gitlab/git/repository.rb', line 221

def branch_exists?(name)
  wrapped_gitaly_errors do
    gitaly_ref_exists?("refs/heads/#{name}")
  end
end

#branch_namesObject

Returns an Array of branch names sorted by name ASC


109
110
111
112
113
# File 'lib/gitlab/git/repository.rb', line 109

def branch_names
  wrapped_gitaly_errors do
    gitaly_ref_client.branch_names
  end
end

#branch_names_contains_sha(sha) ⇒ Object


965
966
967
# File 'lib/gitlab/git/repository.rb', line 965

def branch_names_contains_sha(sha)
  gitaly_ref_client.branch_names_contains_sha(sha)
end

#branchesObject

Returns an Array of Branches


116
117
118
119
120
# File 'lib/gitlab/git/repository.rb', line 116

def branches
  wrapped_gitaly_errors do
    gitaly_ref_client.branches
  end
end

#bundle_to_disk(save_path) ⇒ Object


867
868
869
870
871
872
873
# File 'lib/gitlab/git/repository.rb', line 867

def bundle_to_disk(save_path)
  wrapped_gitaly_errors do
    gitaly_repository_client.create_bundle(save_path)
  end

  true
end

#can_be_merged?(source_sha, target_branch) ⇒ Boolean

Returns:

  • (Boolean)

982
983
984
985
986
987
988
# File 'lib/gitlab/git/repository.rb', line 982

def can_be_merged?(source_sha, target_branch)
  if target_sha = find_branch(target_branch)&.target
    !gitaly_conflicts_client(source_sha, target_sha).conflicts?
  else
    false
  end
end

#checksumObject


1025
1026
1027
1028
1029
1030
1031
1032
# File 'lib/gitlab/git/repository.rb', line 1025

def checksum
  # The exists? RPC is much cheaper, so we perform this request first
  raise NoRepository, "Repository does not exists" unless exists?

  gitaly_repository_client.calculate_checksum
rescue GRPC::NotFound
  raise NoRepository # Guard against data races.
end

#cherry_pick(user:, commit:, branch_name:, message:, start_branch_name:, start_repository:, dry_run: false) ⇒ Object


617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
# File 'lib/gitlab/git/repository.rb', line 617

def cherry_pick(user:, commit:, branch_name:, message:, start_branch_name:, start_repository:, dry_run: false)
  args = {
    user: user,
    commit: commit,
    branch_name: branch_name,
    message: message,
    start_branch_name: start_branch_name,
    start_repository: start_repository,
    dry_run: dry_run
  }

  wrapped_gitaly_errors do
    gitaly_operation_client.user_cherry_pick(args)
  end
end

#clean(options = {}) ⇒ Object

Mimic the `git clean` command and recursively delete untracked files. Valid keys that can be passed in the options hash are:

:d - Remove untracked directories :f - Remove untracked directories that are managed by a different

repository

:x - Remove ignored files

The value in options must evaluate to true for an option to take effect.

Examples:

repo.clean(d: true, f: true) # Enable the -d and -f options

repo.clean(d: false, x: true) # -x is enabled, -d is not

541
542
543
544
545
546
547
# File 'lib/gitlab/git/repository.rb', line 541

def clean(options = {})
  strategies = [:remove_untracked]
  strategies.push(:force) if options[:f]
  strategies.push(:remove_ignored) if options[:x]

  # TODO: implement this method
end

#clean_stale_repository_filesObject


953
954
955
956
957
958
959
960
961
962
963
# File 'lib/gitlab/git/repository.rb', line 953

def clean_stale_repository_files
  wrapped_gitaly_errors do
    gitaly_repository_client.cleanup if exists?
  end
rescue Gitlab::Git::CommandError => e # Don't fail if we can't cleanup
  Gitlab::AppLogger.error("Unable to clean repository on storage #{storage} with relative path #{relative_path}: #{e.message}")
  Gitlab::Metrics.counter(
    :failed_repository_cleanup_total,
    'Number of failed repository cleanup events'
  ).increment
end

#commit(ref = 'HEAD') ⇒ Object

Refactoring aid; allows us to copy code from app/models/repository.rb


770
771
772
# File 'lib/gitlab/git/repository.rb', line 770

def commit(ref = 'HEAD')
  Gitlab::Git::Commit.find(self, ref)
end

#commit_count(ref) ⇒ Object

Return total commits count accessible from passed ref


512
513
514
515
516
# File 'lib/gitlab/git/repository.rb', line 512

def commit_count(ref)
  wrapped_gitaly_errors do
    gitaly_commit_client.commit_count(ref)
  end
end

#compare_source_branch(target_branch_name, source_repository, source_branch_name, straight:) ⇒ Object


755
756
757
758
759
# File 'lib/gitlab/git/repository.rb', line 755

def compare_source_branch(target_branch_name, source_repository, source_branch_name, straight:)
  CrossRepoComparer
    .new(source_repository, self)
    .compare(source_branch_name, target_branch_name, straight: straight)
end

#copy_gitattributes(ref) ⇒ Object


701
702
703
704
705
# File 'lib/gitlab/git/repository.rb', line 701

def copy_gitattributes(ref)
  wrapped_gitaly_errors do
    gitaly_repository_client.apply_gitattributes(ref)
  end
end

#count_commits(options) ⇒ Object


368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
# File 'lib/gitlab/git/repository.rb', line 368

def count_commits(options)
  options = process_count_commits_options(options.dup)

  wrapped_gitaly_errors do
    if options[:left_right]
      from = options[:from]
      to = options[:to]

      right_count = gitaly_commit_client
        .commit_count("#{from}..#{to}", options)
      left_count = gitaly_commit_client
        .commit_count("#{to}..#{from}", options)

      [left_count, right_count]
    else
      gitaly_commit_client.commit_count(options[:ref], options)
    end
  end
end

#count_commits_between(from, to, options = {}) ⇒ Object

Counts the amount of commits between `from` and `to`.


389
390
391
# File 'lib/gitlab/git/repository.rb', line 389

def count_commits_between(from, to, options = {})
  count_commits(from: from, to: to, **options)
end

#create_branch(ref, start_point = "HEAD") ⇒ Object

Create a new branch named **ref+ based on **stat_point+, HEAD by default Note: No Git hooks are executed for this action

Examples:

create_branch("feature")
create_branch("other-feature", "master")

667
668
669
# File 'lib/gitlab/git/repository.rb', line 667

def create_branch(ref, start_point = "HEAD")
  write_ref(ref, start_point)
end

#create_from_bundle(bundle_path) ⇒ Object


821
822
823
824
825
826
827
828
# File 'lib/gitlab/git/repository.rb', line 821

def create_from_bundle(bundle_path)
  # It's important to check that the linked-to file is actually a valid
  # .bundle file as it is passed to `git clone`, which may otherwise
  # interpret it as a pointer to another repository
  ::Gitlab::Git::BundleFile.check!(bundle_path)

  gitaly_repository_client.create_from_bundle(bundle_path)
end

#create_from_snapshot(url, auth) ⇒ Object


830
831
832
# File 'lib/gitlab/git/repository.rb', line 830

def create_from_snapshot(url, auth)
  gitaly_repository_client.create_from_snapshot(url, auth)
end

#create_repositoryObject


101
102
103
104
105
# File 'lib/gitlab/git/repository.rb', line 101

def create_repository
  wrapped_gitaly_errors do
    gitaly_repository_client.create_repository
  end
end

#delete_all_refs_except(prefixes) ⇒ Object


232
233
234
235
236
# File 'lib/gitlab/git/repository.rb', line 232

def delete_all_refs_except(prefixes)
  wrapped_gitaly_errors do
    gitaly_ref_client.delete_refs(except_with_prefixes: prefixes)
  end
end

#delete_branch(branch_name) ⇒ Object

Delete the specified branch from the repository Note: No Git hooks are executed for this action


649
650
651
652
653
# File 'lib/gitlab/git/repository.rb', line 649

def delete_branch(branch_name)
  write_ref(branch_name, Gitlab::Git::BLANK_SHA)
rescue CommandError => e
  raise DeleteBranchError, e
end

#delete_config(*keys) ⇒ Object


905
906
907
908
909
# File 'lib/gitlab/git/repository.rb', line 905

def delete_config(*keys)
  wrapped_gitaly_errors do
    gitaly_repository_client.delete_config(keys)
  end
end

#delete_refs(*ref_names) ⇒ Object


655
656
657
658
659
# File 'lib/gitlab/git/repository.rb', line 655

def delete_refs(*ref_names)
  wrapped_gitaly_errors do
    gitaly_delete_refs(*ref_names)
  end
end

#diff(from, to, options = {}, *paths) ⇒ Object

Return an array of Diff objects that represent the diff between from and to. See Diff::filter_diff_options for the allowed diff options. The options hash can also include :break_rewrites to split larger rewrites into delete/add pairs.


443
444
445
446
447
# File 'lib/gitlab/git/repository.rb', line 443

def diff(from, to, options = {}, *paths)
  iterator = gitaly_commit_client.diff(from, to, options.merge(paths: paths))

  Gitlab::Git::DiffCollection.new(iterator, options)
end

#diff_stats(left_id, right_id) ⇒ Object


449
450
451
452
453
454
455
456
457
458
459
460
461
# File 'lib/gitlab/git/repository.rb', line 449

def diff_stats(left_id, right_id)
  if [left_id, right_id].any? { |ref| ref.blank? || Gitlab::Git.blank_ref?(ref) }
    return empty_diff_stats
  end

  stats = wrapped_gitaly_errors do
    gitaly_commit_client.diff_stats(left_id, right_id)
  end

  Gitlab::Git::DiffStatsCollection.new(stats)
rescue CommandError, TypeError
  empty_diff_stats
end

#disconnect_alternatesObject


911
912
913
914
915
# File 'lib/gitlab/git/repository.rb', line 911

def disconnect_alternates
  wrapped_gitaly_errors do
    gitaly_repository_client.disconnect_alternates
  end
end

#diverging_commit_count(from, to, max_count: 0) ⇒ Object

Return total diverging commits count


519
520
521
522
523
# File 'lib/gitlab/git/repository.rb', line 519

def diverging_commit_count(from, to, max_count: 0)
  wrapped_gitaly_errors do
    gitaly_commit_client.diverging_commit_count(from, to, max_count: max_count)
  end
end

#empty?Boolean

Returns:

  • (Boolean)

774
775
776
# File 'lib/gitlab/git/repository.rb', line 774

def empty?
  !has_visible_content?
end

#exists?Boolean

Returns:

  • (Boolean)

97
98
99
# File 'lib/gitlab/git/repository.rb', line 97

def exists?
  gitaly_repository_client.exists?
end

#expire_has_local_branches_cacheObject


161
162
163
# File 'lib/gitlab/git/repository.rb', line 161

def expire_has_local_branches_cache
  clear_memoization(:has_local_branches)
end

#fetch_remote(remote, ssh_auth: nil, forced: false, no_tags: false, prune: true) ⇒ Object

Fetch remote for repository

remote - remote name ssh_auth - SSH known_hosts data and a private key to use for public-key authentication forced - should we use –force flag? no_tags - should we use –no-tags flag? prune - should we use –prune flag?


785
786
787
788
789
790
791
792
793
794
795
796
# File 'lib/gitlab/git/repository.rb', line 785

def fetch_remote(remote, ssh_auth: nil, forced: false, no_tags: false, prune: true)
  wrapped_gitaly_errors do
    gitaly_repository_client.fetch_remote(
      remote,
      ssh_auth: ssh_auth,
      forced: forced,
      no_tags: no_tags,
      prune: prune,
      timeout: GITLAB_PROJECTS_TIMEOUT
    )
  end
end

#fetch_source_branch!(source_repository, source_branch, local_ref) ⇒ Object


749
750
751
752
753
# File 'lib/gitlab/git/repository.rb', line 749

def fetch_source_branch!(source_repository, source_branch, local_ref)
  wrapped_gitaly_errors do
    gitaly_repository_client.fetch_source_branch(source_repository, source_branch, local_ref)
  end
end

#ff_merge(user, source_sha, target_branch) ⇒ Object


595
596
597
598
599
# File 'lib/gitlab/git/repository.rb', line 595

def ff_merge(user, source_sha, target_branch)
  wrapped_gitaly_errors do
    gitaly_operation_client.user_ff_branch(user, source_sha, target_branch)
  end
end

#find_branch(name) ⇒ Object

Directly find a branch with a simple name (e.g. master)


124
125
126
127
128
# File 'lib/gitlab/git/repository.rb', line 124

def find_branch(name)
  wrapped_gitaly_errors do
    gitaly_ref_client.find_branch(name)
  end
end

#find_commits_by_message(query, ref, path, limit, offset) ⇒ Object


999
1000
1001
1002
1003
1004
1005
# File 'lib/gitlab/git/repository.rb', line 999

def find_commits_by_message(query, ref, path, limit, offset)
  wrapped_gitaly_errors do
    gitaly_commit_client
      .commits_by_message(query, revision: ref, path: path, limit: limit, offset: offset)
      .map { |c| commit(c) }
  end
end

#find_remote_root_ref(remote_name) ⇒ Object


684
685
686
687
688
689
690
# File 'lib/gitlab/git/repository.rb', line 684

def find_remote_root_ref(remote_name)
  return unless remote_name.present?

  wrapped_gitaly_errors do
    gitaly_remote_client.find_remote_root_ref(remote_name)
  end
end

#find_tag(name) ⇒ Object


579
580
581
# File 'lib/gitlab/git/repository.rb', line 579

def find_tag(name)
  tags.find { |tag| tag.name == name }
end

#fsckObject

Raises:


815
816
817
818
819
# File 'lib/gitlab/git/repository.rb', line 815

def fsck
  msg, status = gitaly_repository_client.fsck

  raise GitError.new("Could not fsck repository: #{msg}") unless status == 0
end

#gitaly_blob_clientObject


941
942
943
# File 'lib/gitlab/git/repository.rb', line 941

def gitaly_blob_client
  @gitaly_blob_client ||= Gitlab::GitalyClient::BlobService.new(self)
end

#gitaly_commit_clientObject


925
926
927
# File 'lib/gitlab/git/repository.rb', line 925

def gitaly_commit_client
  @gitaly_commit_client ||= Gitlab::GitalyClient::CommitService.new(self)
end

#gitaly_conflicts_client(our_commit_oid, their_commit_oid) ⇒ Object


945
946
947
# File 'lib/gitlab/git/repository.rb', line 945

def gitaly_conflicts_client(our_commit_oid, their_commit_oid)
  Gitlab::GitalyClient::ConflictsService.new(self, our_commit_oid, their_commit_oid)
end

#gitaly_operation_clientObject


933
934
935
# File 'lib/gitlab/git/repository.rb', line 933

def gitaly_operation_client
  @gitaly_operation_client ||= Gitlab::GitalyClient::OperationService.new(self)
end

#gitaly_ref_clientObject


921
922
923
# File 'lib/gitlab/git/repository.rb', line 921

def gitaly_ref_client
  @gitaly_ref_client ||= Gitlab::GitalyClient::RefService.new(self)
end

#gitaly_remote_clientObject


937
938
939
# File 'lib/gitlab/git/repository.rb', line 937

def gitaly_remote_client
  @gitaly_remote_client ||= Gitlab::GitalyClient::RemoteService.new(self)
end

#gitaly_repositoryObject


917
918
919
# File 'lib/gitlab/git/repository.rb', line 917

def gitaly_repository
  Gitlab::GitalyClient::Util.repository(@storage, @relative_path, @gl_repository, @gl_project_path)
end

#gitaly_repository_clientObject


929
930
931
# File 'lib/gitlab/git/repository.rb', line 929

def gitaly_repository_client
  @gitaly_repository_client ||= Gitlab::GitalyClient::RepositoryService.new(self)
end

#gitattribute(path, name) ⇒ Object


721
722
723
# File 'lib/gitlab/git/repository.rb', line 721

def gitattribute(path, name)
  attributes(path)[name]
end

#has_local_branches?Boolean Also known as: has_visible_content?

Returns:

  • (Boolean)

165
166
167
168
169
# File 'lib/gitlab/git/repository.rb', line 165

def has_local_branches?
  strong_memoize(:has_local_branches) do
    uncached_has_local_branches?
  end
end

#hashObject


77
78
79
# File 'lib/gitlab/git/repository.rb', line 77

def hash
  [self.class, storage, relative_path].hash
end

#import_repository(url) ⇒ Object

Raises:

  • (ArgumentError)

798
799
800
801
802
803
804
# File 'lib/gitlab/git/repository.rb', line 798

def import_repository(url)
  raise ArgumentError, "don't use disk paths with import_repository: #{url.inspect}" if url.start_with?('.', '/')

  wrapped_gitaly_errors do
    gitaly_repository_client.import_repository(url)
  end
end

#info_attributesObject


707
708
709
710
711
712
# File 'lib/gitlab/git/repository.rb', line 707

def info_attributes
  return @info_attributes if @info_attributes

  content = gitaly_repository_client.info_attributes
  @info_attributes = AttributesParser.new(content)
end

#languages(ref = nil) ⇒ Object


737
738
739
740
741
# File 'lib/gitlab/git/repository.rb', line 737

def languages(ref = nil)
  wrapped_gitaly_errors do
    gitaly_commit_client.languages(ref)
  end
end

#last_commit_for_path(sha, path, literal_pathspec: false) ⇒ Object


1019
1020
1021
1022
1023
# File 'lib/gitlab/git/repository.rb', line 1019

def last_commit_for_path(sha, path, literal_pathspec: false)
  wrapped_gitaly_errors do
    gitaly_commit_client.last_commit_for_path(sha, path, literal_pathspec: literal_pathspec)
  end
end

#license_short_nameObject


743
744
745
746
747
# File 'lib/gitlab/git/repository.rb', line 743

def license_short_name
  wrapped_gitaly_errors do
    gitaly_repository_client.license_short_name
  end
end

#list_commits_by_ref_name(refs) ⇒ Object


1013
1014
1015
1016
1017
# File 'lib/gitlab/git/repository.rb', line 1013

def list_commits_by_ref_name(refs)
  wrapped_gitaly_errors do
    gitaly_commit_client.list_commits_by_ref_name(refs)
  end
end

#list_last_commits_for_tree(sha, path, offset: 0, limit: 25, literal_pathspec: false) ⇒ Object


1007
1008
1009
1010
1011
# File 'lib/gitlab/git/repository.rb', line 1007

def list_last_commits_for_tree(sha, path, offset: 0, limit: 25, literal_pathspec: false)
  wrapped_gitaly_errors do
    gitaly_commit_client.list_last_commits_for_tree(sha, path, offset: offset, limit: limit, literal_pathspec: literal_pathspec)
  end
end

#local_branches(sort_by: nil, pagination_params: nil) ⇒ Object


130
131
132
133
134
# File 'lib/gitlab/git/repository.rb', line 130

def local_branches(sort_by: nil, pagination_params: nil)
  wrapped_gitaly_errors do
    gitaly_ref_client.local_branches(sort_by: sort_by, pagination_params: pagination_params)
  end
end

#log(options) ⇒ Object

Build an array of commits.

Usage.

repo.log(
  ref: 'master',
  path: 'app/models',
  limit: 10,
  offset: 5,
  after: Time.new(2016, 4, 21, 14, 32, 10)
)

326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
# File 'lib/gitlab/git/repository.rb', line 326

def log(options)
  default_options = {
    limit: 10,
    offset: 0,
    path: nil,
    author: nil,
    follow: false,
    skip_merges: false,
    after: nil,
    before: nil,
    all: false
  }

  options = default_options.merge(options)
  options[:offset] ||= 0

  limit = options[:limit]
  if limit == 0 || !limit.is_a?(Integer)
    raise ArgumentError.new("invalid Repository#log limit: #{limit.inspect}")
  end

  wrapped_gitaly_errors do
    gitaly_commit_client.find_commits(options)
  end
end

#ls_files(ref) ⇒ Object

Returns result like “git ls-files” , recursive and full file path

Ex.

repo.ls_files('master')

697
698
699
# File 'lib/gitlab/git/repository.rb', line 697

def ls_files(ref)
  gitaly_commit_client.ls_files(ref)
end

#merge(user, source_sha, target_branch, message, &block) ⇒ Object


589
590
591
592
593
# File 'lib/gitlab/git/repository.rb', line 589

def merge(user, source_sha, target_branch, message, &block)
  wrapped_gitaly_errors do
    gitaly_operation_client.user_merge_branch(user, source_sha, target_branch, message, &block)
  end
end

#merge_base(*commits) ⇒ Object

Returns the SHA of the most recent common ancestor of from and to


414
415
416
417
418
# File 'lib/gitlab/git/repository.rb', line 414

def merge_base(*commits)
  wrapped_gitaly_errors do
    gitaly_repository_client.find_merge_base(*commits)
  end
end

#merge_to_ref(user, source_sha, branch, target_ref, message, first_parent_ref) ⇒ Object


583
584
585
586
587
# File 'lib/gitlab/git/repository.rb', line 583

def merge_to_ref(user, source_sha, branch, target_ref, message, first_parent_ref)
  wrapped_gitaly_errors do
    gitaly_operation_client.user_merge_to_ref(user, source_sha, branch, target_ref, message, first_parent_ref)
  end
end

#merged_branch_names(branch_names = []) ⇒ Object


425
426
427
428
429
430
431
432
433
434
435
436
437
# File 'lib/gitlab/git/repository.rb', line 425

def merged_branch_names(branch_names = [])
  return [] unless root_ref

  root_sha = find_branch(root_ref)&.target

  return [] unless root_sha

  branches = wrapped_gitaly_errors do
    gitaly_merged_branch_names(branch_names, root_sha)
  end

  Set.new(branches)
end

#multi_action(user, branch_name:, message:, actions:, author_email: nil, author_name: nil, start_branch_name: nil, start_sha: nil, start_repository: self, force: false) ⇒ Object

rubocop:disable Metrics/ParameterLists


876
877
878
879
880
881
882
883
884
885
886
887
# File 'lib/gitlab/git/repository.rb', line 876

def multi_action(
  user, branch_name:, message:, actions:,
  author_email: nil, author_name: nil,
  start_branch_name: nil, start_sha: nil, start_repository: self,
  force: false)

  wrapped_gitaly_errors do
    gitaly_operation_client.user_commit_files(user, branch_name,
        message, actions, author_email, author_name,
        start_branch_name, start_repository, force, start_sha)
  end
end

#new_blobs(newrev, dynamic_timeout: nil) ⇒ Object


358
359
360
361
362
363
364
365
366
# File 'lib/gitlab/git/repository.rb', line 358

def new_blobs(newrev, dynamic_timeout: nil)
  return [] if newrev.blank? || newrev == ::Gitlab::Git::BLANK_SHA

  strong_memoize("new_blobs_#{newrev}") do
    wrapped_gitaly_errors do
      gitaly_ref_client.list_new_blobs(newrev, REV_LIST_COMMIT_LIMIT, dynamic_timeout: dynamic_timeout)
    end
  end
end

#new_commits(newrev) ⇒ Object


352
353
354
355
356
# File 'lib/gitlab/git/repository.rb', line 352

def new_commits(newrev)
  wrapped_gitaly_errors do
    gitaly_ref_client.list_new_commits(newrev)
  end
end

#object_directory_sizeObject

Return git object directory size in bytes


312
313
314
# File 'lib/gitlab/git/repository.rb', line 312

def object_directory_size
  gitaly_repository_client.get_object_directory_size.to_f * 1024
end

#pathObject

This method will be removed when Gitaly reaches v1.1.


82
83
84
85
86
# File 'lib/gitlab/git/repository.rb', line 82

def path
  File.join(
    Gitlab.config.repositories.storages[@storage].legacy_disk_path, @relative_path
  )
end

#praefect_info_clientObject


949
950
951
# File 'lib/gitlab/git/repository.rb', line 949

def praefect_info_client
  @praefect_info_client ||= Gitlab::GitalyClient::PraefectInfoService.new(self)
end

#raw_changes_between(old_rev, new_rev) ⇒ Object

old_rev and new_rev are commit ID's the result of this method is an array of Gitlab::Git::RawDiffChange


395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
# File 'lib/gitlab/git/repository.rb', line 395

def raw_changes_between(old_rev, new_rev)
  @raw_changes_between ||= {}

  @raw_changes_between[[old_rev, new_rev]] ||=
    begin
      return [] if new_rev.blank? || new_rev == Gitlab::Git::BLANK_SHA

      wrapped_gitaly_errors do
        gitaly_repository_client.raw_changes_between(old_rev, new_rev)
          .each_with_object([]) do |msg, arr|
          msg.raw_changes.each { |change| arr << ::Gitlab::Git::RawDiffChange.new(change) }
        end
      end
    end
rescue ArgumentError => e
  raise Gitlab::Git::Repository::GitError.new(e)
end

#rebase(user, rebase_id, branch:, branch_sha:, remote_repository:, remote_branch:, push_options: [], &block) ⇒ Object


834
835
836
837
838
839
840
841
842
843
844
845
846
847
# File 'lib/gitlab/git/repository.rb', line 834

def rebase(user, rebase_id, branch:, branch_sha:, remote_repository:, remote_branch:, push_options: [], &block)
  wrapped_gitaly_errors do
    gitaly_operation_client.rebase(
      user,
      rebase_id,
      branch: branch,
      branch_sha: branch_sha,
      remote_repository: remote_repository,
      remote_branch: remote_branch,
      push_options: push_options,
      &block
    )
  end
end

#rebase_in_progress?(rebase_id) ⇒ Boolean

Returns:

  • (Boolean)

849
850
851
852
853
# File 'lib/gitlab/git/repository.rb', line 849

def rebase_in_progress?(rebase_id)
  wrapped_gitaly_errors do
    gitaly_repository_client.rebase_in_progress?(rebase_id)
  end
end

#ref_exists?(ref_name) ⇒ Boolean

Returns true if the given ref name exists

Ref names must start with `refs/`.

Returns:

  • (Boolean)

203
204
205
206
207
# File 'lib/gitlab/git/repository.rb', line 203

def ref_exists?(ref_name)
  wrapped_gitaly_errors do
    gitaly_ref_exists?(ref_name)
  end
end

#ref_name_for_sha(ref_path, sha) ⇒ Object

Returns a RefName for a given SHA

Raises:

  • (ArgumentError)

464
465
466
467
468
# File 'lib/gitlab/git/repository.rb', line 464

def ref_name_for_sha(ref_path, sha)
  raise ArgumentError, "sha can't be empty" unless sha.present?

  gitaly_ref_client.find_ref_name(sha, ref_path)
end

#ref_namesObject

Returns an Array of branch and tag names


228
229
230
# File 'lib/gitlab/git/repository.rb', line 228

def ref_names
  branch_names + tag_names
end

#refs_hashObject

Get refs hash which key is the commit id and value is a Gitlab::Git::Tag or Gitlab::Git::Branch Note that both inherit from Gitlab::Git::Ref


473
474
475
476
477
478
479
480
481
482
483
484
485
# File 'lib/gitlab/git/repository.rb', line 473

def refs_hash
  return @refs_hash if @refs_hash

  @refs_hash = Hash.new { |h, k| h[k] = [] }

  (tags + branches).each do |ref|
    next unless ref.target && ref.name && ref.dereferenced_target&.id

    @refs_hash[ref.dereferenced_target.id] << ref.name
  end

  @refs_hash
end

#removeObject


149
150
151
152
153
# File 'lib/gitlab/git/repository.rb', line 149

def remove
  wrapped_gitaly_errors do
    gitaly_repository_client.remove
  end
end

#remove_remote(remote_name) ⇒ Object


678
679
680
681
682
# File 'lib/gitlab/git/repository.rb', line 678

def remove_remote(remote_name)
  wrapped_gitaly_errors do
    gitaly_remote_client.remove_remote(remote_name)
  end
end

#rename(new_relative_path) ⇒ Object


143
144
145
146
147
# File 'lib/gitlab/git/repository.rb', line 143

def rename(new_relative_path)
  wrapped_gitaly_errors do
    gitaly_repository_client.rename(new_relative_path)
  end
end

#replicasObject


1034
1035
1036
1037
1038
# File 'lib/gitlab/git/repository.rb', line 1034

def replicas
  wrapped_gitaly_errors do
    praefect_info_client.replicas
  end
end

#replicate(source_repository) ⇒ Object


155
156
157
158
159
# File 'lib/gitlab/git/repository.rb', line 155

def replicate(source_repository)
  wrapped_gitaly_errors do
    gitaly_repository_client.replicate(source_repository)
  end
end

#revert(user:, commit:, branch_name:, message:, start_branch_name:, start_repository:, dry_run: false) ⇒ Object


601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
# File 'lib/gitlab/git/repository.rb', line 601

def revert(user:, commit:, branch_name:, message:, start_branch_name:, start_repository:, dry_run: false)
  args = {
    user: user,
    commit: commit,
    branch_name: branch_name,
    message: message,
    start_branch_name: start_branch_name,
    start_repository: start_repository,
    dry_run: dry_run
  }

  wrapped_gitaly_errors do
    gitaly_operation_client.user_revert(args)
  end
end

#rm_branch(branch_name, user:) ⇒ Object


567
568
569
570
571
# File 'lib/gitlab/git/repository.rb', line 567

def rm_branch(branch_name, user:)
  wrapped_gitaly_errors do
    gitaly_operation_client.user_delete_branch(branch_name, user)
  end
end

#rm_tag(tag_name, user:) ⇒ Object


573
574
575
576
577
# File 'lib/gitlab/git/repository.rb', line 573

def rm_tag(tag_name, user:)
  wrapped_gitaly_errors do
    gitaly_operation_client.rm_tag(tag_name, user)
  end
end

#root_refObject

Default branch in the repository


89
90
91
92
93
94
95
# File 'lib/gitlab/git/repository.rb', line 89

def root_ref
  gitaly_ref_client.default_branch_name
rescue GRPC::NotFound => e
  raise NoRepository.new(e.message)
rescue GRPC::Unknown => e
  raise Gitlab::Git::CommandError.new(e.message)
end

#search_files_by_content(query, ref, options = {}) ⇒ Object


973
974
975
976
977
978
979
980
# File 'lib/gitlab/git/repository.rb', line 973

def search_files_by_content(query, ref, options = {})
  return [] if empty? || query.blank?

  safe_query = Regexp.escape(query)
  ref ||= root_ref

  gitaly_repository_client.search_files_by_content(ref, safe_query, options)
end

#search_files_by_name(query, ref) ⇒ Object


990
991
992
993
994
995
996
997
# File 'lib/gitlab/git/repository.rb', line 990

def search_files_by_name(query, ref)
  safe_query = Regexp.escape(query.sub(%r{^/*}, ""))
  ref ||= root_ref

  return [] if empty? || safe_query.blank?

  gitaly_repository_client.search_files_by_name(ref, safe_query)
end

#set_config(entries) ⇒ Object


899
900
901
902
903
# File 'lib/gitlab/git/repository.rb', line 899

def set_config(entries)
  wrapped_gitaly_errors do
    gitaly_repository_client.set_config(entries)
  end
end

#sizeObject

Return repo size in megabytes


305
306
307
308
309
# File 'lib/gitlab/git/repository.rb', line 305

def size
  size = gitaly_repository_client.repository_size

  (size.to_f / 1024).round(2)
end

#squash(user, squash_id, start_sha:, end_sha:, author:, message:) ⇒ Object


855
856
857
858
859
# File 'lib/gitlab/git/repository.rb', line 855

def squash(user, squash_id, start_sha:, end_sha:, author:, message:)
  wrapped_gitaly_errors do
    gitaly_operation_client.user_squash(user, squash_id, start_sha, end_sha, author, message)
  end
end

#squash_in_progress?(squash_id) ⇒ Boolean

Returns:

  • (Boolean)

861
862
863
864
865
# File 'lib/gitlab/git/repository.rb', line 861

def squash_in_progress?(squash_id)
  wrapped_gitaly_errors do
    gitaly_repository_client.squash_in_progress?(squash_id)
  end
end

#submodule_url_for(ref, path) ⇒ Object

Returns url for submodule

Ex.

@repository.submodule_url_for('master', 'rack')
# => [email protected]:rack.git

493
494
495
496
497
# File 'lib/gitlab/git/repository.rb', line 493

def submodule_url_for(ref, path)
  wrapped_gitaly_errors do
    gitaly_submodule_url_for(ref, path)
  end
end

#submodule_urls_for(ref) ⇒ Object

Returns path to url mappings for submodules

Ex.

@repository.submodule_urls_for('master')
# => { 'rack' => '[email protected]:rack.git' }

505
506
507
508
509
# File 'lib/gitlab/git/repository.rb', line 505

def submodule_urls_for(ref)
  wrapped_gitaly_errors do
    gitaly_submodule_urls_for(ref)
  end
end

#tag_countObject

Returns the number of valid tags


179
180
181
182
183
# File 'lib/gitlab/git/repository.rb', line 179

def tag_count
  wrapped_gitaly_errors do
    gitaly_ref_client.count_tag_names
  end
end

#tag_exists?(name) ⇒ Boolean

Returns true if the given tag exists

name - The name of the tag as a String.

Returns:

  • (Boolean)

212
213
214
215
216
# File 'lib/gitlab/git/repository.rb', line 212

def tag_exists?(name)
  wrapped_gitaly_errors do
    gitaly_ref_exists?("refs/tags/#{name}")
  end
end

#tag_namesObject

Returns an Array of tag names


186
187
188
189
190
# File 'lib/gitlab/git/repository.rb', line 186

def tag_names
  wrapped_gitaly_errors do
    gitaly_ref_client.tag_names
  end
end

#tag_names_contains_sha(sha) ⇒ Object


969
970
971
# File 'lib/gitlab/git/repository.rb', line 969

def tag_names_contains_sha(sha)
  gitaly_ref_client.tag_names_contains_sha(sha)
end

#tagsObject

Returns an Array of Tags


194
195
196
197
198
# File 'lib/gitlab/git/repository.rb', line 194

def tags
  wrapped_gitaly_errors do
    gitaly_ref_client.tags
  end
end

#to_sObject


67
68
69
# File 'lib/gitlab/git/repository.rb', line 67

def to_s
  "<#{self.class.name}: #{self.gl_project_path}>"
end

#update_branch(branch_name, user:, newrev:, oldrev:) ⇒ Object


561
562
563
564
565
# File 'lib/gitlab/git/repository.rb', line 561

def update_branch(branch_name, user:, newrev:, oldrev:)
  wrapped_gitaly_errors do
    gitaly_operation_client.user_update_branch(branch_name, user, newrev, oldrev)
  end
end

#update_submodule(user:, submodule:, commit_sha:, message:, branch:) ⇒ Object


633
634
635
636
637
638
639
640
641
642
643
644
645
# File 'lib/gitlab/git/repository.rb', line 633

def update_submodule(user:, submodule:, commit_sha:, message:, branch:)
  args = {
    user: user,
    submodule: submodule,
    commit_sha: commit_sha,
    branch: branch,
    message: message
  }

  wrapped_gitaly_errors do
    gitaly_operation_client.user_update_submodule(args)
  end
end

#write_config(full_path:) ⇒ Object

rubocop:enable Metrics/ParameterLists

Raises:


890
891
892
893
894
895
896
897
# File 'lib/gitlab/git/repository.rb', line 890

def write_config(full_path:)
  return unless full_path.present?

  # This guard avoids Gitaly log/error spam
  raise NoRepository, 'repository does not exist' unless exists?

  set_config('gitlab.fullpath' => full_path)
end

#write_ref(ref_path, ref, old_ref: nil) ⇒ Object


761
762
763
764
765
766
767
# File 'lib/gitlab/git/repository.rb', line 761

def write_ref(ref_path, ref, old_ref: nil)
  ref_path = "#{Gitlab::Git::BRANCH_REF_PREFIX}#{ref_path}" unless ref_path.start_with?("refs/") || ref_path == "HEAD"

  wrapped_gitaly_errors do
    gitaly_repository_client.write_ref(ref_path, ref, old_ref)
  end
end