git-multirepo 1.0.0.beta45 → 1.0.0.beta46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/.gitattributes +2 -2
  3. data/.gitbugtraq +3 -3
  4. data/.gitignore +38 -38
  5. data/.multirepo +21 -21
  6. data/.multirepo.lock +26 -26
  7. data/.multirepo.meta +2 -2
  8. data/.rspec +2 -2
  9. data/Gemfile +4 -4
  10. data/Gemfile.lock +42 -42
  11. data/LICENSE +22 -22
  12. data/README.md +154 -144
  13. data/Rakefile +2 -2
  14. data/bin/multi +10 -10
  15. data/docs/bug-repros/91565510-repro.sh +20 -20
  16. data/docs/git-multirepo-cheatsheet.docx +0 -0
  17. data/git-multirepo.gemspec +31 -31
  18. data/lib/commands.rb +14 -14
  19. data/lib/git-multirepo.rb +2 -2
  20. data/lib/info.rb +4 -4
  21. data/lib/multirepo/commands/add-command.rb +50 -50
  22. data/lib/multirepo/commands/branch-command.rb +81 -81
  23. data/lib/multirepo/commands/checkout-command.rb +119 -119
  24. data/lib/multirepo/commands/clone-command.rb +67 -67
  25. data/lib/multirepo/commands/command.rb +89 -89
  26. data/lib/multirepo/commands/do-command.rb +102 -100
  27. data/lib/multirepo/commands/graph-command.rb +42 -42
  28. data/lib/multirepo/commands/init-command.rb +119 -119
  29. data/lib/multirepo/commands/inspect-command.rb +38 -39
  30. data/lib/multirepo/commands/install-command.rb +146 -137
  31. data/lib/multirepo/commands/merge-command.rb +225 -225
  32. data/lib/multirepo/commands/open-command.rb +56 -55
  33. data/lib/multirepo/commands/remove-command.rb +47 -47
  34. data/lib/multirepo/commands/uninit-command.rb +17 -17
  35. data/lib/multirepo/commands/update-command.rb +55 -55
  36. data/lib/multirepo/config.rb +15 -15
  37. data/lib/multirepo/files/config-entry.rb +38 -38
  38. data/lib/multirepo/files/config-file.rb +45 -45
  39. data/lib/multirepo/files/lock-entry.rb +28 -28
  40. data/lib/multirepo/files/lock-file.rb +55 -55
  41. data/lib/multirepo/files/meta-file.rb +40 -40
  42. data/lib/multirepo/files/tracking-file.rb +8 -8
  43. data/lib/multirepo/files/tracking-files.rb +46 -46
  44. data/lib/multirepo/git/branch.rb +31 -31
  45. data/lib/multirepo/git/change.rb +10 -10
  46. data/lib/multirepo/git/commit.rb +6 -6
  47. data/lib/multirepo/git/git-runner.rb +46 -46
  48. data/lib/multirepo/git/git.rb +9 -9
  49. data/lib/multirepo/git/ref.rb +37 -37
  50. data/lib/multirepo/git/remote.rb +16 -16
  51. data/lib/multirepo/git/repo.rb +122 -122
  52. data/lib/multirepo/hooks/post-commit-hook.rb +22 -22
  53. data/lib/multirepo/hooks/pre-commit-hook.rb +34 -34
  54. data/lib/multirepo/logic/dependency.rb +5 -5
  55. data/lib/multirepo/logic/merge-descriptor.rb +94 -94
  56. data/lib/multirepo/logic/node.rb +71 -71
  57. data/lib/multirepo/logic/performer.rb +56 -56
  58. data/lib/multirepo/logic/revision-selector.rb +34 -34
  59. data/lib/multirepo/multirepo-exception.rb +5 -5
  60. data/lib/multirepo/utility/console.rb +51 -51
  61. data/lib/multirepo/utility/runner.rb +34 -34
  62. data/lib/multirepo/utility/utils.rb +94 -94
  63. data/resources/.gitconfig +2 -2
  64. data/resources/post-commit +5 -5
  65. data/resources/pre-commit +5 -5
  66. data/spec/integration/init_spec.rb +18 -18
  67. data/spec/spec_helper.rb +89 -89
  68. metadata +2 -2
@@ -1,47 +1,47 @@
1
- require "multirepo/git/git-runner"
2
- require_relative "meta-file"
3
- require_relative "lock-file"
4
-
5
- module MultiRepo
6
- class TrackingFiles
7
- attr_accessor :files
8
-
9
- def initialize(path)
10
- @path = path
11
- @files = [MetaFile.new(path), LockFile.new(path)]
12
- end
13
-
14
- def update
15
- updated = false
16
- files.each { |f| updated |= f.update }
17
- return updated
18
- end
19
-
20
- def stage
21
- GitRunner.run_in_working_dir(@path, "add --force -- #{files_pathspec}", Runner::Verbosity::OUTPUT_ON_ERROR)
22
- end
23
-
24
- def commit(message)
25
- stage
26
-
27
- output = GitRunner.run_in_working_dir(@path, "ls-files --modified --others -- #{files_pathspec}", Runner::Verbosity::OUTPUT_NEVER)
28
- files_are_untracked_or_modified = output.strip != ""
29
-
30
- output = GitRunner.run_in_working_dir(@path, "diff --name-only --cached -- #{files_pathspec}", Runner::Verbosity::OUTPUT_NEVER)
31
- files_are_staged = output.strip != ""
32
-
33
- must_commit = files_are_untracked_or_modified || files_are_staged
34
- GitRunner.run_in_working_dir(@path, "commit --no-verify -m \"#{message}\" --only -- #{files_pathspec}", Runner::Verbosity::OUTPUT_ON_ERROR) if must_commit
35
-
36
- return must_commit
37
- end
38
-
39
- def delete
40
- files.each { |f| FileUtils.rm_f(f.file) }
41
- end
42
-
43
- def files_pathspec
44
- files.map{ |f| File.basename(f.file) }.join(" ")
45
- end
46
- end
1
+ require "multirepo/git/git-runner"
2
+ require_relative "meta-file"
3
+ require_relative "lock-file"
4
+
5
+ module MultiRepo
6
+ class TrackingFiles
7
+ attr_accessor :files
8
+
9
+ def initialize(path)
10
+ @path = path
11
+ @files = [MetaFile.new(path), LockFile.new(path)]
12
+ end
13
+
14
+ def update
15
+ updated = false
16
+ files.each { |f| updated |= f.update }
17
+ return updated
18
+ end
19
+
20
+ def stage
21
+ GitRunner.run_in_working_dir(@path, "add --force -- #{files_pathspec}", Runner::Verbosity::OUTPUT_ON_ERROR)
22
+ end
23
+
24
+ def commit(message)
25
+ stage
26
+
27
+ output = GitRunner.run_in_working_dir(@path, "ls-files --modified --others -- #{files_pathspec}", Runner::Verbosity::OUTPUT_NEVER)
28
+ files_are_untracked_or_modified = output.strip != ""
29
+
30
+ output = GitRunner.run_in_working_dir(@path, "diff --name-only --cached -- #{files_pathspec}", Runner::Verbosity::OUTPUT_NEVER)
31
+ files_are_staged = output.strip != ""
32
+
33
+ must_commit = files_are_untracked_or_modified || files_are_staged
34
+ GitRunner.run_in_working_dir(@path, "commit --no-verify -m \"#{message}\" --only -- #{files_pathspec}", Runner::Verbosity::OUTPUT_ON_ERROR) if must_commit
35
+
36
+ return must_commit
37
+ end
38
+
39
+ def delete
40
+ files.each { |f| FileUtils.rm_f(f.file) }
41
+ end
42
+
43
+ def files_pathspec
44
+ files.map{ |f| File.basename(f.file) }.join(" ")
45
+ end
46
+ end
47
47
  end
@@ -1,32 +1,32 @@
1
- require_relative "ref"
2
- require_relative "git-runner"
3
-
4
- module MultiRepo
5
- class Branch < Ref
6
- def exists?
7
- lines = GitRunner.run_in_working_dir(@repo.path, "branch", Runner::Verbosity::OUTPUT_NEVER).split("\n")
8
- branch_names = lines.map { |line| line.tr("* ", "")}
9
- branch_names.include?(@name)
10
- end
11
-
12
- def upstream_branch
13
- output = GitRunner.run_in_working_dir(@repo.path, "config --get branch.#{@name}.merge", Runner::Verbosity::OUTPUT_NEVER)
14
- output.sub!("refs/heads/", "")
15
- return nil if output == ""
16
- Branch.new(@repo, "origin/#{output}")
17
- end
18
-
19
- def create
20
- GitRunner.run_in_working_dir(@repo.path, "branch #{@name}", Runner::Verbosity::OUTPUT_ON_ERROR)
21
- end
22
-
23
- def push
24
- GitRunner.run_in_working_dir(@repo.path, "push -u origin #{@name}", Runner::Verbosity::OUTPUT_ON_ERROR)
25
- end
26
-
27
- def checkout
28
- GitRunner.run_in_working_dir(@repo.path, "checkout #{@name}", Runner::Verbosity::OUTPUT_ON_ERROR)
29
- GitRunner.last_command_succeeded
30
- end
31
- end
1
+ require_relative "ref"
2
+ require_relative "git-runner"
3
+
4
+ module MultiRepo
5
+ class Branch < Ref
6
+ def exists?
7
+ lines = GitRunner.run_in_working_dir(@repo.path, "branch", Runner::Verbosity::OUTPUT_NEVER).split("\n")
8
+ branch_names = lines.map { |line| line.tr("* ", "")}
9
+ branch_names.include?(@name)
10
+ end
11
+
12
+ def upstream_branch
13
+ output = GitRunner.run_in_working_dir(@repo.path, "config --get branch.#{@name}.merge", Runner::Verbosity::OUTPUT_NEVER)
14
+ output.sub!("refs/heads/", "")
15
+ return nil if output == ""
16
+ Branch.new(@repo, "origin/#{output}")
17
+ end
18
+
19
+ def create
20
+ GitRunner.run_in_working_dir(@repo.path, "branch #{@name}", Runner::Verbosity::OUTPUT_ON_ERROR)
21
+ end
22
+
23
+ def push
24
+ GitRunner.run_in_working_dir(@repo.path, "push -u origin #{@name}", Runner::Verbosity::OUTPUT_ON_ERROR)
25
+ end
26
+
27
+ def checkout
28
+ GitRunner.run_in_working_dir(@repo.path, "checkout #{@name}", Runner::Verbosity::OUTPUT_ON_ERROR)
29
+ GitRunner.last_command_succeeded
30
+ end
31
+ end
32
32
  end
@@ -1,11 +1,11 @@
1
- module MultiRepo
2
- class Change
3
- attr_accessor :status
4
- attr_accessor :path
5
-
6
- def initialize(line)
7
- @status = line[0...2].strip
8
- @path = line[3..-1]
9
- end
10
- end
1
+ module MultiRepo
2
+ class Change
3
+ attr_accessor :status
4
+ attr_accessor :path
5
+
6
+ def initialize(line)
7
+ @status = line[0...2].strip
8
+ @path = line[3..-1]
9
+ end
10
+ end
11
11
  end
@@ -1,7 +1,7 @@
1
- require_relative "ref"
2
- require_relative "git-runner"
3
-
4
- module MultiRepo
5
- class Commit < Ref
6
- end
1
+ require_relative "ref"
2
+ require_relative "git-runner"
3
+
4
+ module MultiRepo
5
+ class Commit < Ref
6
+ end
7
7
  end
@@ -1,47 +1,47 @@
1
- require "multirepo/utility/runner"
2
- require "multirepo/config"
3
-
4
- module MultiRepo
5
- class GitRunner
6
- class << self
7
- attr_accessor :last_command_succeeded
8
- end
9
-
10
- def self.run_in_current_dir(git_command, verbosity)
11
- full_command = "#{git_executable} #{git_command}"
12
- run(full_command, verbosity)
13
- end
14
-
15
- def self.run_in_working_dir(path, git_command, verbosity)
16
- if path == "."
17
- # It is always better to skip -C when running git commands in the
18
- # current directory (especially in hooks). Doing this prevents
19
- # any future issues because we automatically fallback to non-"-C" for ".".
20
- # Fixes bug: https://www.pivotaltracker.com/story/show/94505654
21
- return run_in_current_dir(git_command, verbosity)
22
- else
23
- full_command = "#{git_executable} -C \"#{path}\" #{git_command}";
24
- end
25
-
26
- # True fix for the -C flag issue in pre-commit hook where the status command would
27
- # fail to provide correct results if a pathspec was provided when performing a commit.
28
- # http://thread.gmane.org/gmane.comp.version-control.git/263319/focus=263323
29
-
30
- if Config.instance.running_git_hook
31
- full_command = "sh -c 'unset $(git rev-parse --local-env-vars); #{full_command};'"
32
- end
33
-
34
- run(full_command, verbosity)
35
- end
36
-
37
- def self.run(full_command, verbosity)
38
- result = Runner.run(full_command, verbosity)
39
- @last_command_succeeded = Runner.last_command_succeeded
40
- return result
41
- end
42
-
43
- def self.git_executable
44
- Config.instance.git_executable || "git"
45
- end
46
- end
1
+ require "multirepo/utility/runner"
2
+ require "multirepo/config"
3
+
4
+ module MultiRepo
5
+ class GitRunner
6
+ class << self
7
+ attr_accessor :last_command_succeeded
8
+ end
9
+
10
+ def self.run_in_current_dir(git_command, verbosity)
11
+ full_command = "#{git_executable} #{git_command}"
12
+ run(full_command, verbosity)
13
+ end
14
+
15
+ def self.run_in_working_dir(path, git_command, verbosity)
16
+ if path == "."
17
+ # It is always better to skip -C when running git commands in the
18
+ # current directory (especially in hooks). Doing this prevents
19
+ # any future issues because we automatically fallback to non-"-C" for ".".
20
+ # Fixes bug: https://www.pivotaltracker.com/story/show/94505654
21
+ return run_in_current_dir(git_command, verbosity)
22
+ else
23
+ full_command = "#{git_executable} -C \"#{path}\" #{git_command}";
24
+ end
25
+
26
+ # True fix for the -C flag issue in pre-commit hook where the status command would
27
+ # fail to provide correct results if a pathspec was provided when performing a commit.
28
+ # http://thread.gmane.org/gmane.comp.version-control.git/263319/focus=263323
29
+
30
+ if Config.instance.running_git_hook
31
+ full_command = "sh -c 'unset $(git rev-parse --local-env-vars); #{full_command};'"
32
+ end
33
+
34
+ run(full_command, verbosity)
35
+ end
36
+
37
+ def self.run(full_command, verbosity)
38
+ result = Runner.run(full_command, verbosity)
39
+ @last_command_succeeded = Runner.last_command_succeeded
40
+ return result
41
+ end
42
+
43
+ def self.git_executable
44
+ Config.instance.git_executable || "git"
45
+ end
46
+ end
47
47
  end
@@ -1,10 +1,10 @@
1
- require "multirepo/git/git-runner"
2
-
3
- module MultiRepo
4
- class Git
5
- def self.valid_branch_name?(name)
6
- GitRunner.run_in_current_dir("check-ref-format --branch \"#{name}\"", Runner::Verbosity::OUTPUT_NEVER)
7
- GitRunner.last_command_succeeded
8
- end
9
- end
1
+ require "multirepo/git/git-runner"
2
+
3
+ module MultiRepo
4
+ class Git
5
+ def self.valid_branch_name?(name)
6
+ GitRunner.run_in_current_dir("check-ref-format --branch \"#{name}\"", Runner::Verbosity::OUTPUT_NEVER)
7
+ GitRunner.last_command_succeeded
8
+ end
9
+ end
10
10
  end
@@ -1,38 +1,38 @@
1
- require_relative "git-runner"
2
-
3
- module MultiRepo
4
- class Ref
5
- attr_accessor :name
6
-
7
- def initialize(repo, name)
8
- @repo = repo
9
- @name = name
10
- end
11
-
12
- def exists?
13
- output = GitRunner.run_in_working_dir(@repo.path, "rev-parse --verify --quiet #{@name}", Runner::Verbosity::OUTPUT_NEVER).strip
14
- return output != ""
15
- end
16
-
17
- def commit_id
18
- GitRunner.run_in_working_dir(@repo.path, "rev-parse #{@name}", Runner::Verbosity::OUTPUT_NEVER).strip
19
- end
20
-
21
- def short_commit_id
22
- GitRunner.run_in_working_dir(@repo.path, "rev-parse --short #{@name}", Runner::Verbosity::OUTPUT_NEVER).strip
23
- end
24
-
25
- def is_merge?
26
- lines = GitRunner.run_in_working_dir(@repo.path, "cat-file -p #{@name}", Runner::Verbosity::OUTPUT_NEVER).split("\n")
27
- parents = lines.grep(/^parent /)
28
- return parents.count > 1
29
- end
30
-
31
- def can_fast_forward_to?(ref)
32
- # http://stackoverflow.com/a/2934062/167983
33
- rev_parse_output = GitRunner.run_in_working_dir(@repo.path, "rev-parse #{@name}", Runner::Verbosity::OUTPUT_NEVER)
34
- merge_base_output = GitRunner.run_in_working_dir(@repo.path, "merge-base \"#{rev_parse_output}\" \"#{ref.name}\"", Runner::Verbosity::OUTPUT_NEVER)
35
- return merge_base_output == rev_parse_output
36
- end
37
- end
1
+ require_relative "git-runner"
2
+
3
+ module MultiRepo
4
+ class Ref
5
+ attr_accessor :name
6
+
7
+ def initialize(repo, name)
8
+ @repo = repo
9
+ @name = name
10
+ end
11
+
12
+ def exists?
13
+ output = GitRunner.run_in_working_dir(@repo.path, "rev-parse --verify --quiet #{@name}", Runner::Verbosity::OUTPUT_NEVER).strip
14
+ return output != ""
15
+ end
16
+
17
+ def commit_id
18
+ GitRunner.run_in_working_dir(@repo.path, "rev-parse #{@name}", Runner::Verbosity::OUTPUT_NEVER).strip
19
+ end
20
+
21
+ def short_commit_id
22
+ GitRunner.run_in_working_dir(@repo.path, "rev-parse --short #{@name}", Runner::Verbosity::OUTPUT_NEVER).strip
23
+ end
24
+
25
+ def is_merge?
26
+ lines = GitRunner.run_in_working_dir(@repo.path, "cat-file -p #{@name}", Runner::Verbosity::OUTPUT_NEVER).split("\n")
27
+ parents = lines.grep(/^parent /)
28
+ return parents.count > 1
29
+ end
30
+
31
+ def can_fast_forward_to?(ref)
32
+ # http://stackoverflow.com/a/2934062/167983
33
+ rev_parse_output = GitRunner.run_in_working_dir(@repo.path, "rev-parse #{@name}", Runner::Verbosity::OUTPUT_NEVER)
34
+ merge_base_output = GitRunner.run_in_working_dir(@repo.path, "merge-base \"#{rev_parse_output}\" \"#{ref.name}\"", Runner::Verbosity::OUTPUT_NEVER)
35
+ return merge_base_output == rev_parse_output
36
+ end
37
+ end
38
38
  end
@@ -1,17 +1,17 @@
1
- require_relative "git-runner"
2
-
3
- module MultiRepo
4
- class Remote
5
- attr_accessor :name
6
-
7
- def initialize(repo, name)
8
- @repo = repo
9
- @name = name
10
- end
11
-
12
- def url
13
- output = GitRunner.run_in_working_dir(@repo.path, "config --get remote.#{@name}.url", Runner::Verbosity::OUTPUT_NEVER).strip
14
- return output == "" ? nil : output
15
- end
16
- end
1
+ require_relative "git-runner"
2
+
3
+ module MultiRepo
4
+ class Remote
5
+ attr_accessor :name
6
+
7
+ def initialize(repo, name)
8
+ @repo = repo
9
+ @name = name
10
+ end
11
+
12
+ def url
13
+ output = GitRunner.run_in_working_dir(@repo.path, "config --get remote.#{@name}.url", Runner::Verbosity::OUTPUT_NEVER).strip
14
+ return output == "" ? nil : output
15
+ end
16
+ end
17
17
  end
@@ -1,123 +1,123 @@
1
- require_relative "branch"
2
- require_relative "remote"
3
- require_relative "commit"
4
- require_relative "change"
5
-
6
- module MultiRepo
7
- class Repo
8
- attr_accessor :path
9
- attr_accessor :basename
10
-
11
- def initialize(path)
12
- @path = path
13
- @basename = Pathname.new(path).basename.to_s
14
- end
15
-
16
- # Inspection
17
-
18
- def exists?
19
- return false unless Dir.exist?("#{@path}/.git")
20
- return GitRunner.run_in_working_dir(@path, "rev-parse --is-inside-work-tree", Runner::Verbosity::OUTPUT_NEVER).strip == "true"
21
- end
22
-
23
- def head_born?
24
- result = GitRunner.run_in_working_dir(@path, "rev-parse HEAD --", Runner::Verbosity::OUTPUT_NEVER).strip
25
- return !result.start_with?("fatal: bad revision")
26
- end
27
-
28
- def current_revision
29
- (current_branch || current_commit).name
30
- end
31
-
32
- def clean?
33
- changes.count == 0
34
- end
35
-
36
- def local_branches
37
- branches_by_removing_prefix(/^refs\/heads\//)
38
- end
39
-
40
- def remote_branches
41
- branches_by_removing_prefix(/^refs\/remotes\//)
42
- end
43
-
44
- def changes
45
- output = GitRunner.run_in_working_dir(@path, "status --porcelain", Runner::Verbosity::OUTPUT_NEVER)
46
- lines = output.split("\n").each{ |f| f.strip }.delete_if{ |f| f == "" }
47
- lines.map { |l| Change.new(l) }
48
- end
49
-
50
- # Operations
51
-
52
- def fetch
53
- GitRunner.run_in_working_dir(@path, "fetch --prune --progress", Runner::Verbosity::OUTPUT_ALWAYS)
54
- Runner.last_command_succeeded
55
- end
56
-
57
- def clone(url, branch = nil)
58
- if branch != nil
59
- GitRunner.run_in_current_dir("clone #{url} -b #{branch} #{@path} --progress", Runner::Verbosity::OUTPUT_ALWAYS)
60
- else
61
- GitRunner.run_in_current_dir("clone #{url} #{@path} --progress", Runner::Verbosity::OUTPUT_ALWAYS)
62
- end
63
- Runner.last_command_succeeded
64
- end
65
-
66
- def checkout(ref_name)
67
- GitRunner.run_in_working_dir(@path, "checkout #{ref_name}", Runner::Verbosity::OUTPUT_ON_ERROR)
68
- Runner.last_command_succeeded
69
- end
70
-
71
- # Current
72
-
73
- def head
74
- return nil unless exists? && head_born?
75
- Ref.new(self, "HEAD")
76
- end
77
-
78
- def current_commit
79
- return nil unless exists? && head_born?
80
- Commit.new(self, head.commit_id)
81
- end
82
-
83
- def current_branch
84
- return nil unless exists? && head_born?
85
- name = GitRunner.run_in_working_dir(@path, "rev-parse --abbrev-ref HEAD", Runner::Verbosity::OUTPUT_NEVER).strip
86
- Branch.new(self, name)
87
- end
88
-
89
- # Factory methods
90
-
91
- def ref(name)
92
- Ref.new(self, name)
93
- end
94
-
95
- def branch(name)
96
- Branch.new(self, name)
97
- end
98
-
99
- def remote(name)
100
- Remote.new(self, name)
101
- end
102
-
103
- def commit(id)
104
- Commit.new(self, id)
105
- end
106
-
107
- # Private helper methods
108
-
109
- private
110
-
111
- def branches_by_removing_prefix(prefix_regex)
112
- output = GitRunner.run_in_working_dir(@path, "for-each-ref --format='%(refname)'", Runner::Verbosity::OUTPUT_NEVER)
113
- all_refs = output.strip.split("\n")
114
-
115
- # Remove surrounding quotes on Windows
116
- all_refs = all_refs.map { |l| l.sub(/^\'/, "").sub(/\'$/, "") }
117
-
118
- full_names = all_refs.select { |r| r =~ prefix_regex }
119
- names = full_names.map{ |f| f.sub(prefix_regex, "") }.delete_if{ |n| n =~ /HEAD$/}
120
- names.map { |b| Branch.new(self, b) }
121
- end
122
- end
1
+ require_relative "branch"
2
+ require_relative "remote"
3
+ require_relative "commit"
4
+ require_relative "change"
5
+
6
+ module MultiRepo
7
+ class Repo
8
+ attr_accessor :path
9
+ attr_accessor :basename
10
+
11
+ def initialize(path)
12
+ @path = path
13
+ @basename = Pathname.new(path).basename.to_s
14
+ end
15
+
16
+ # Inspection
17
+
18
+ def exists?
19
+ return false unless Dir.exist?("#{@path}/.git")
20
+ return GitRunner.run_in_working_dir(@path, "rev-parse --is-inside-work-tree", Runner::Verbosity::OUTPUT_NEVER).strip == "true"
21
+ end
22
+
23
+ def head_born?
24
+ result = GitRunner.run_in_working_dir(@path, "rev-parse HEAD --", Runner::Verbosity::OUTPUT_NEVER).strip
25
+ return !result.start_with?("fatal: bad revision")
26
+ end
27
+
28
+ def current_revision
29
+ (current_branch || current_commit).name
30
+ end
31
+
32
+ def clean?
33
+ changes.count == 0
34
+ end
35
+
36
+ def local_branches
37
+ branches_by_removing_prefix(/^refs\/heads\//)
38
+ end
39
+
40
+ def remote_branches
41
+ branches_by_removing_prefix(/^refs\/remotes\//)
42
+ end
43
+
44
+ def changes
45
+ output = GitRunner.run_in_working_dir(@path, "status --porcelain", Runner::Verbosity::OUTPUT_NEVER)
46
+ lines = output.split("\n").each{ |f| f.strip }.delete_if{ |f| f == "" }
47
+ lines.map { |l| Change.new(l) }
48
+ end
49
+
50
+ # Operations
51
+
52
+ def fetch
53
+ GitRunner.run_in_working_dir(@path, "fetch --prune --progress", Runner::Verbosity::OUTPUT_ALWAYS)
54
+ Runner.last_command_succeeded
55
+ end
56
+
57
+ def clone(url, branch = nil)
58
+ if branch != nil
59
+ GitRunner.run_in_current_dir("clone #{url} -b #{branch} #{@path} --progress", Runner::Verbosity::OUTPUT_ALWAYS)
60
+ else
61
+ GitRunner.run_in_current_dir("clone #{url} #{@path} --progress", Runner::Verbosity::OUTPUT_ALWAYS)
62
+ end
63
+ Runner.last_command_succeeded
64
+ end
65
+
66
+ def checkout(ref_name)
67
+ GitRunner.run_in_working_dir(@path, "checkout #{ref_name}", Runner::Verbosity::OUTPUT_ON_ERROR)
68
+ Runner.last_command_succeeded
69
+ end
70
+
71
+ # Current
72
+
73
+ def head
74
+ return nil unless exists? && head_born?
75
+ Ref.new(self, "HEAD")
76
+ end
77
+
78
+ def current_commit
79
+ return nil unless exists? && head_born?
80
+ Commit.new(self, head.commit_id)
81
+ end
82
+
83
+ def current_branch
84
+ return nil unless exists? && head_born?
85
+ name = GitRunner.run_in_working_dir(@path, "rev-parse --abbrev-ref HEAD", Runner::Verbosity::OUTPUT_NEVER).strip
86
+ Branch.new(self, name)
87
+ end
88
+
89
+ # Factory methods
90
+
91
+ def ref(name)
92
+ Ref.new(self, name)
93
+ end
94
+
95
+ def branch(name)
96
+ Branch.new(self, name)
97
+ end
98
+
99
+ def remote(name)
100
+ Remote.new(self, name)
101
+ end
102
+
103
+ def commit(id)
104
+ Commit.new(self, id)
105
+ end
106
+
107
+ # Private helper methods
108
+
109
+ private
110
+
111
+ def branches_by_removing_prefix(prefix_regex)
112
+ output = GitRunner.run_in_working_dir(@path, "for-each-ref --format='%(refname)'", Runner::Verbosity::OUTPUT_NEVER)
113
+ all_refs = output.strip.split("\n")
114
+
115
+ # Remove surrounding quotes on Windows
116
+ all_refs = all_refs.map { |l| l.sub(/^\'/, "").sub(/\'$/, "") }
117
+
118
+ full_names = all_refs.select { |r| r =~ prefix_regex }
119
+ names = full_names.map{ |f| f.sub(prefix_regex, "") }.delete_if{ |n| n =~ /HEAD$/}
120
+ names.map { |b| Branch.new(self, b) }
121
+ end
122
+ end
123
123
  end