cimas 0.1.4 → 0.3.0

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.
@@ -0,0 +1,136 @@
1
+ require "git"
2
+ require "open3"
3
+
4
+ module Cimas
5
+ # Deep module over one repository's working copy: every git-gem call,
6
+ # exception rescue, and porcelain/CLI parsing the orchestrator needs
7
+ # lives behind ~10 domain verbs. Subcommands then read as wave prose
8
+ # instead of git incantations.
9
+ class WorkingCopy
10
+ def self.open(dir)
11
+ new(Git.open(dir))
12
+ end
13
+
14
+ def self.clone(remote, name, path:)
15
+ Git.clone(remote, name, path: path)
16
+ end
17
+
18
+ def initialize(git)
19
+ @git = git
20
+ end
21
+
22
+ def head_sha
23
+ @git.object("HEAD").sha
24
+ end
25
+
26
+ def remote_name
27
+ @git.remotes.first.to_s
28
+ end
29
+
30
+ # True when the working copy has any uncommitted change.
31
+ def drift?
32
+ out, = Open3.capture3("git", "-C", dir, "status", "--porcelain")
33
+ !out.strip.empty?
34
+ rescue StandardError
35
+ false
36
+ end
37
+
38
+ # checkout + reset_hard + clean: the "start from a pristine branch"
39
+ # preamble of sync and cleanup-orphan-files.
40
+ def reset_clean(branch, include_untracked: false)
41
+ checkout(branch)
42
+ @git.reset_hard(branch)
43
+ @git.clean(force: true, d: include_untracked)
44
+ end
45
+
46
+ def stage(*paths)
47
+ paths.each { |path| @git.add(path) }
48
+ end
49
+
50
+ def remove(*paths)
51
+ paths.each { |path| @git.remove(path) }
52
+ end
53
+
54
+ # True when nothing is staged or modified — push skips the
55
+ # commit in this case.
56
+ def clean?
57
+ status = @git.status
58
+ status.changed.empty? && status.added.empty? && status.deleted.empty?
59
+ end
60
+
61
+ def commit_all(message)
62
+ @git.commit_all(message)
63
+ end
64
+
65
+ def commit(message)
66
+ @git.commit(message)
67
+ end
68
+
69
+ # push's keep_changes=false preamble: stand on the base branch,
70
+ # soft-reset onto it, and discard an existing branch of the given
71
+ # name so it can be provisioned fresh.
72
+ def reset_onto(base, discard_branch: nil)
73
+ checkout(base)
74
+ @git.reset(base)
75
+ @git.branch(discard_branch).delete if discard_branch && @git.is_branch?(discard_branch)
76
+ end
77
+
78
+ # Switch to a branch; :fresh discards an existing branch of the same
79
+ # name first (cleanup-orphan-files' shape).
80
+ def switch_branch(name, fresh: false)
81
+ @git.branch(name).delete if fresh && @git.is_branch?(name)
82
+ @git.branch(name).checkout
83
+ end
84
+
85
+ # pull's dance: fetch origin, best-effort reset (a fresh clone may
86
+ # not hold the branch ref yet), checkout, pull.
87
+ def fetch_reset_pull(branch)
88
+ @git.remote("origin").fetch
89
+ begin
90
+ @git.reset_hard(branch)
91
+ rescue Git::Error
92
+ # reset can fail when the branch ref is absent on a fresh
93
+ # clone; checkout/pull below still applies.
94
+ nil
95
+ end
96
+ checkout(branch)
97
+ @git.pull("origin", branch)
98
+ end
99
+
100
+ # Returns :pushed, :behind_remote (non-force push behind the remote
101
+ # tip), or [:rejected, error]. The caller decides presentation.
102
+ # "Updates were rejected" covers both the classic git phrasing
103
+ # ("tip of your current branch is behind") and the modern one ("the
104
+ # remote contains work that you do not have locally").
105
+ def push(branch, force: false, remote: nil)
106
+ @git.push(remote || remote_name, branch, force: force)
107
+ :pushed
108
+ rescue Git::GitExecuteError, Git::FailedError => e
109
+ if e.message.match?(/Updates were rejected/)
110
+ :behind_remote
111
+ else
112
+ [:rejected, e]
113
+ end
114
+ end
115
+
116
+ def diff_patch
117
+ @git.diff.patch
118
+ end
119
+
120
+ def each_staged_change
121
+ @git.status.changed.each do |file, status|
122
+ yield(file, status.blob(:index).contents)
123
+ end
124
+ end
125
+
126
+ private
127
+
128
+ def checkout(branch)
129
+ @git.checkout(branch)
130
+ end
131
+
132
+ def dir
133
+ @git.dir.to_s
134
+ end
135
+ end
136
+ end
data/lib/cimas.rb CHANGED
@@ -1,8 +1,27 @@
1
- require "cimas/version"
2
- require "cimas/cli/command"
3
- require "cimas/repository"
1
+ require "pathname"
4
2
 
5
3
  module Cimas
4
+ # Ownership marker cimas writes into every file it generates and
5
+ # detects when deciding whether a file is cimas-managed (orphan
6
+ # cleanup). One definition — sync and cleanup must agree on it.
7
+ GENERATED_HEADER_MARKER = "Auto-generated by Cimas"
8
+ GENERATED_HEADER = [
9
+ "# #{GENERATED_HEADER_MARKER}: Do not edit it manually!",
10
+ "# See https://github.com/metanorma/cimas",
11
+ ].join("\n").freeze
12
+
13
+ # Autoload paths are absolute (resolved from __dir__) so a plain
14
+ # `require_relative` boot from exe/cimas works from any cwd, without
15
+ # $LOAD_PATH setup.
16
+ autoload :VERSION, File.expand_path("cimas/version", __dir__)
17
+ autoload :Repository, File.expand_path("cimas/repository", __dir__)
18
+ autoload :Patch, File.expand_path("cimas/patch", __dir__)
19
+ autoload :OrphanFiles, File.expand_path("cimas/orphan_files", __dir__)
20
+ autoload :WorkingCopy, File.expand_path("cimas/working_copy", __dir__)
21
+ autoload :GitHub, File.expand_path("cimas/github", __dir__)
22
+ autoload :ReleasePreflight, File.expand_path("cimas/release_preflight", __dir__)
23
+ autoload :Cli, File.expand_path("cimas/cli", __dir__)
24
+
6
25
  def self.root
7
26
  File.dirname(__dir__)
8
27
  end