Class: Mortar::Git::Git

Inherits:
Object
  • Object
show all
Defined in:
lib/mortar/git.rb

Constant Summary collapse

GIT_STATUS_CODES__CONFLICT =
Set.new ["DD", "AU", "UD", "UA", "DU", "AA", "UU"]

Instance Method Summary collapse

Instance Method Details

#add(path) ⇒ Object

add



467
468
469
# File 'lib/mortar/git.rb', line 467

def add(path)
  git("add #{path}")
end

#add_entry_to_mortar_project_manifest(path, entry) ⇒ Object



163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/mortar/git.rb', line 163

def add_entry_to_mortar_project_manifest(path, entry)
  contents = File.open(path, "r") do |manifest|
    manifest.read.strip
  end

  if contents && (! contents.include? entry)
    new_contents = "#{contents}\n#{entry}\n"
    File.open(path, "w") do |manifest|
      manifest.write new_contents
    end
  end
end

#add_newline_to_file(path) ⇒ Object



176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/mortar/git.rb', line 176

def add_newline_to_file(path)
  File.open(path, "r+") do |manifest|
    contents = manifest.read()
    manifest.seek(0, IO::SEEK_END)

    # `contents` in ruby 1.8.7 is array with entries of the
    # type Fixnum which isn't semantically comparable with
    # the \n char, but the ascii code 10 is
    unless (contents[-1] == "\n" or contents[-1] == 10)
      manifest.puts "" # ensure file ends with a newline
    end
  end
end

#all_branchesObject

Includes remote tracking branches.



482
483
484
# File 'lib/mortar/git.rb', line 482

def all_branches
  git("branch --all")
end

#branch_delete(branch_name) ⇒ Object



499
500
501
# File 'lib/mortar/git.rb', line 499

def branch_delete(branch_name)
  git("branch -D #{branch_name}")
end

#branchesObject

branch



475
476
477
# File 'lib/mortar/git.rb', line 475

def branches
  git("branch")
end

#clone(git_url, path = "", remote_name = "origin") ⇒ Object

clone



632
633
634
# File 'lib/mortar/git.rb', line 632

def clone(git_url, path="", remote_name="origin")
  git("clone -o %s %s \"%s\"" % [remote_name, git_url, path], true, false)
end

#contains_hash(hash) ⇒ Object



676
677
678
# File 'lib/mortar/git.rb', line 676

def contains_hash(hash)
  git("log --pretty=\"%H\"").include?(hash)
end

#copy_files_to_dir(pathlist, dest_dir) ⇒ Object



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/mortar/git.rb', line 119

def copy_files_to_dir(pathlist, dest_dir)
  #Used to copy the pathlist from the manifest to a separate directory
  #before syncing.
  pathlist.each do |path|
    dir, file = File.split(path)

    #For non-root files/directories we need to create the parent
    #directories before copying.
    unless dir == "."
      FileUtils.mkdir_p(File.join(dest_dir, dir))
    end

    FileUtils.cp_r(path, File.join(dest_dir, dir))
  end

end

#create_and_push_snapshot_branch(project) ⇒ Object



310
311
312
313
314
315
316
317
318
319
320
321
322
323
# File 'lib/mortar/git.rb', line 310

def create_and_push_snapshot_branch(project)
  curdir = Dir.pwd

  # create a snapshot branch in a temporary directory
  snapshot_dir, snapshot_branch = Helpers.action("Taking code snapshot") do
    create_snapshot_branch()
  end

  Dir.chdir(snapshot_dir)
  git_ref = push_with_retry(project.remote, snapshot_branch, "Sending code snapshot to Mortar")
  FileUtils.remove_entry_secure(snapshot_dir)
  Dir.chdir(curdir)
  return git_ref
end

#create_mortar_project_manifest(path) ⇒ Object

Create a project manifest file



252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
# File 'lib/mortar/git.rb', line 252

def create_mortar_project_manifest(path)
  File.open("#{path}/#{project_manifest_name}", 'w') do |manifest|
    manifest.puts "macros"
    manifest.puts "pigscripts"
    manifest.puts "udfs"

    if File.directory? "#{path}/lib"
      manifest.puts "lib"
    end

    if File.directory? "#{path}/luigiscripts"
      manifest.puts "luigiscripts"
    end

    if File.directory? "#{path}/sparkscripts"
      manifest.puts "sparkscripts"
    end

    if File.exists? "#{path}/.gitignore"
      manifest.puts ".gitignore"
    end

  end
end

#create_snapshot_branchObject

snapshot



281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
# File 'lib/mortar/git.rb', line 281

def create_snapshot_branch
  # TODO: handle Ctrl-C in the middle
  unless has_commits?
    raise GitError, "No commits found in repository.  You must do an initial commit to initialize the repository."
  end

  # Copy code into a temp directory so we don't confuse editors while snapshotting
  curdir = Dir.pwd
  tmpdir = safe_copy(mortar_manifest_pathlist)

  starting_branch = current_branch
  snapshot_branch = "mortar-snapshot-#{Mortar::UUID.create_random.to_s}"

  # checkout a new branch
  git("checkout -b #{snapshot_branch}")

  # stage all changes (including deletes)
  git("add .")
  git("add -u .")

  # commit the changes if there are any
  if ! is_clean_working_directory?
    git("commit -m \"mortar development snapshot commit\"")
  end

  Dir.chdir(curdir)
  return tmpdir, snapshot_branch
end

#current_branchObject

Raises:



486
487
488
489
490
491
492
493
494
495
496
497
# File 'lib/mortar/git.rb', line 486

def current_branch
  branches.split("\n").each do |branch_listing|
  
    # current branch will be the one that starts with *, e.g.
    #   not_my_current_branch
    # * my_current_branch
    if branch_listing =~ /^\*\s(\S*)/
      return $1
    end
  end
  raise GitError, "Unable to find current branch in list #{branches}"
end

#did_stash_changes?(stash_message) ⇒ Boolean

Returns:

  • (Boolean)


591
592
593
# File 'lib/mortar/git.rb', line 591

def did_stash_changes?(stash_message)
  ! (stash_message.include? "No local changes to save")
end

#ensure_embedded_project_mirror_exists(mirror_dir, git_organization) ⇒ Object



357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
# File 'lib/mortar/git.rb', line 357

def ensure_embedded_project_mirror_exists(mirror_dir, git_organization)
  mirror_dir_git_dir = File.join(mirror_dir, '.git')

  # create and initialize mirror git repo 
  # if it doesn't already exist with data
  unless (File.directory? mirror_dir_git_dir) && 
    (! Dir.glob(File.join(mirror_dir_git_dir, "*")).empty?)

    # remove any existing data from mirror dir
    FileUtils.rm_rf(mirror_dir)

    # create parent dir if it doesn't already exist
    unless File.directory? mortar_mirrors_dir
      FileUtils.mkdir_p mortar_mirrors_dir
    end

    # clone mortar-code repo
    ensure_valid_mortar_project_manifest()
    remote_path = File.open(".mortar-project-remote").read.strip
    clone(remote_path, mirror_dir)

    Dir.chdir(mirror_dir)

    # ensure that the mortar remote is defined
    unless remotes(git_organization).include? "mortar"
      git("remote add mortar #{remote_path}")  
    end

    # make an initial commit to the specified branch
    unless File.exists? ".gitkeep" # flag that signals that the repo has been initialized
                                   # initialization is not necessary if this is not the first user to use it 
      File.open(".gitkeep", "w").close()
      git("add .")
      git("commit -m \"Setting up embedded Mortar project\"")
      push_with_retry("mortar", "master", "Setting up embedded Mortar project")
    end
  end
end

#ensure_gitignore_in_project_manifestObject

Ensure that the .gitignore file is included in every project manifest to prevent syncing ignored files.



242
243
244
245
246
247
# File 'lib/mortar/git.rb', line 242

def ensure_gitignore_in_project_manifest
  gitignore_path = ".gitignore"
  if File.exists? gitignore_path
    add_entry_to_mortar_project_manifest(project_manifest_name, gitignore_path)
  end
end

#ensure_has_gitObject



47
48
49
50
51
# File 'lib/mortar/git.rb', line 47

def ensure_has_git
  unless has_git?
    raise GitError, "git 1.7.7 or higher must be installed"
  end
end

#ensure_luigiscripts_in_project_manifestObject

Ensure that the luigiscripts directory, which was added after some project manifests were created, is in the manifest (if luigiscripts exists).



231
232
233
234
235
236
# File 'lib/mortar/git.rb', line 231

def ensure_luigiscripts_in_project_manifest
  luigiscripts_path = "luigiscripts"
  if File.directory? luigiscripts_path
    add_entry_to_mortar_project_manifest(project_manifest_name, luigiscripts_path)
  end
end

#ensure_sparkscripts_in_project_manifestObject

Ensure that the sparkscripts directory, which was added after some project manifests were created, is in the manifest (if sparkscripts exists).



219
220
221
222
223
224
# File 'lib/mortar/git.rb', line 219

def ensure_sparkscripts_in_project_manifest
  sparkscripts_path = "sparkscripts"
  if File.directory? sparkscripts_path
    add_entry_to_mortar_project_manifest(project_manifest_name, sparkscripts_path)
  end
end

#ensure_valid_mortar_project_manifestObject

Create a snapshot whitelist file if it doesn’t already exist



203
204
205
206
207
208
209
210
211
212
# File 'lib/mortar/git.rb', line 203

def ensure_valid_mortar_project_manifest()
  if File.exists? project_manifest_name
    ensure_sparkscripts_in_project_manifest()
    ensure_luigiscripts_in_project_manifest()
    ensure_gitignore_in_project_manifest()
    add_newline_to_file(project_manifest_name)
  else
    create_mortar_project_manifest('.')
  end
end

#fetch(remote) ⇒ Object



668
669
670
# File 'lib/mortar/git.rb', line 668

def fetch(remote)
  git("fetch #{remote}")
end

#fork_base_remote_nameObject



636
637
638
# File 'lib/mortar/git.rb', line 636

def fork_base_remote_name
  "base"
end

#get_last_recorded_fork_hashObject



655
656
657
658
659
660
661
662
# File 'lib/mortar/git.rb', line 655

def get_last_recorded_fork_hash
  if File.exists?(mortar_fork_meta_file)
    File.open(mortar_fork_meta_file, "r") do |f|
      file_contents = f.read()
      file_contents.strip
    end
  end
end

#get_latest_hash(remote) ⇒ Object



672
673
674
# File 'lib/mortar/git.rb', line 672

def get_latest_hash(remote)
  git("log --pretty=\"%H\" -n 1 #{remote}")
end

#git(args, check_success = true, check_git_directory = true) ⇒ Object



71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/mortar/git.rb', line 71

def git(args, check_success=true, check_git_directory=true)
  ensure_has_git
  if check_git_directory && !has_dot_git?
    raise GitError, "No .git directory found"
  end
  
  flattened_args = [args].flatten.compact.join(" ")
  output = %x{ git #{flattened_args} 2>&1 }.strip
  success = $?.success?
  if check_success && (! success)
    raise GitError, "Error executing 'git #{flattened_args}':\n#{output}"
  end
  output
end

#git_initObject



66
67
68
69
# File 'lib/mortar/git.rb', line 66

def git_init
  ensure_has_git
  run_cmd("git init")
end

#git_ref(refname) ⇒ Object

rev-parse



578
579
580
# File 'lib/mortar/git.rb', line 578

def git_ref(refname)
  git("rev-parse --verify --quiet #{refname}")
end

#has_commits?Boolean

Returns:

  • (Boolean)


604
605
606
607
608
# File 'lib/mortar/git.rb', line 604

def has_commits?
  # see http://stackoverflow.com/a/5492347
  %x{ git rev-parse --verify --quiet HEAD }
  $?.success?
end

#has_conflicts?Boolean

Returns:

  • (Boolean)


616
617
618
619
620
621
622
623
# File 'lib/mortar/git.rb', line 616

def has_conflicts?
  def status_code(status_str)
    status_str[0,2]
  end

  status_codes = status.split("\n").collect{|s| status_code(s)}
  ! GIT_STATUS_CODES__CONFLICT.intersection(status_codes).empty?
end

#has_dot_git?Boolean

Returns:

  • (Boolean)


62
63
64
# File 'lib/mortar/git.rb', line 62

def has_dot_git?
  File.directory?(".git")
end

#has_git?(major_version = 1, minor_version = 7, revision_version = 7) ⇒ Boolean

core commands

Returns:

  • (Boolean)


33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/mortar/git.rb', line 33

def has_git?(major_version=1, minor_version=7, revision_version=7)
  # Needs to have git version 1.7.7 or greater.  Earlier versions lack 
  # the necessary untracked option for stash.
  git_version_output, has_git = run_cmd("git --version")
  if has_git
    git_version = git_version_output.split(" ")[2]
    versions = git_version.split(".")
    is_ok_version = versions[0].to_i >= major_version + 1 ||
                    ( versions[0].to_i == major_version && versions[1].to_i >= minor_version + 1 ) ||
                    ( versions[0].to_i == major_version && versions[1].to_i == minor_version && versions[2].to_i >= revision_version)
  end
  has_git && is_ok_version
end

#is_clean_working_directory?Boolean

Returns:

  • (Boolean)


610
611
612
# File 'lib/mortar/git.rb', line 610

def is_clean_working_directory?
  status.empty?
end

#is_fork_repo_updated(git_organization) ⇒ Object



640
641
642
643
644
645
646
647
648
649
650
651
652
653
# File 'lib/mortar/git.rb', line 640

def is_fork_repo_updated(git_organization)
  if remotes(git_organization).has_key?(fork_base_remote_name)
    fetch(fork_base_remote_name)
    latest_commit = get_latest_hash(fork_base_remote_name)
    last_commit = get_last_recorded_fork_hash
    unless latest_commit == last_commit || contains_hash(latest_commit)
      File.open(mortar_fork_meta_file, "wb") do |f|
        f.write(latest_commit)
      end
      return true
    end
  end
  return false
end

#mortar_fork_meta_fileObject



664
665
666
# File 'lib/mortar/git.rb', line 664

def mortar_fork_meta_file
  ".mortar-fork"
end

#mortar_manifest_pathlist(include_dot_git = true) ⇒ Object

Only snapshot filesystem paths that are in a whitelist



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/mortar/git.rb', line 140

def mortar_manifest_pathlist(include_dot_git = true)
  ensure_valid_mortar_project_manifest()

  manifest_pathlist = File.read(project_manifest_name).split("\n")
  if include_dot_git
    manifest_pathlist << ".git"
  end

  #Strip out comments and empty lines
  manifest_pathlist = manifest_pathlist.select do |path|
    s_path = path.strip
    !s_path.start_with?("#") && !s_path.empty?
  end

  manifest_pathlist.each do |path|
    unless File.exists? path
      Helpers.error("#{project_manifest_name} includes file/dir \"#{path}\" that is not in the mortar project directory.")
    end
  end
  
  manifest_pathlist
end

#mortar_mirrors_dirObject



338
339
340
# File 'lib/mortar/git.rb', line 338

def mortar_mirrors_dir()
  "/tmp/mortar-git-mirrors"
end

#project_manifest_nameObject



190
191
192
193
194
195
196
197
198
# File 'lib/mortar/git.rb', line 190

def project_manifest_name()
  if File.exists? "project.manifest"
    "project.manifest"
  elsif File.exists? ".mortar-project-manifest"
    ".mortar-project-manifest"
  else
    "project.manifest"
  end
end

#pull(remote_name, ref) ⇒ Object

pull



541
542
543
# File 'lib/mortar/git.rb', line 541

def pull(remote_name, ref)
  git("pull #{remote_name} #{ref}")
end

#push(remote_name, ref) ⇒ Object

push



507
508
509
# File 'lib/mortar/git.rb', line 507

def push(remote_name, ref)
  git("push #{remote_name} #{ref}")
end

#push_all(remote_name) ⇒ Object



511
512
513
# File 'lib/mortar/git.rb', line 511

def push_all(remote_name)
  git("push #{remote_name} --all")
end

#push_masterObject



86
87
88
89
90
91
92
93
94
95
96
# File 'lib/mortar/git.rb', line 86

def push_master
  unless has_commits?
    raise GitError, "No commits found in repository.  You must do an initial commit to initialize the repository."
  end

  safe_copy(mortar_manifest_pathlist) do
    did_stash_changes = stash_working_dir("Stash for push to master")
    git('push mortar master')
  end

end

#push_with_retry(remote_name, branch_name, action_msg, push_all_branches = false) ⇒ Object



515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
# File 'lib/mortar/git.rb', line 515

def push_with_retry(remote_name, branch_name, action_msg, push_all_branches = false)
  git_ref = Helpers.action(action_msg) do
    # push the code
    begin
        if push_all_branches
          push_all(remote_name)
        else
          push(remote_name, branch_name)
        end
    rescue
      retry if retry_snapshot_push?
      Helpers.error("Could not connect to github remote. Tried #{@snapshot_push_attempts.to_s} times.")
    end

    # grab the commit hash
    ref = git_ref(branch_name)
    ref
  end

  return git_ref
end

#remote_add(name, url) ⇒ Object



563
564
565
# File 'lib/mortar/git.rb', line 563

def remote_add(name, url)
  git("remote add #{name} #{url}")
end

#remotes(git_organization) ⇒ Object

remotes



549
550
551
552
553
554
555
556
557
558
559
560
561
# File 'lib/mortar/git.rb', line 549

def remotes(git_organization)
  # returns {git_remote_name => project_name}
  remotes = {}
  git("remote -v").split("\n").each do |remote|
    name, url, method = remote.split(/\s/)
    if url =~ /^git@([\w\d\.]+):#{git_organization}\/[a-f0-9]{24}+_([\w\d-]+)\.git$$/ ||
      url =~ /^git@([\w\d\.]+):#{git_organization}\/([\w\d-]+)\.git$$/
      remotes[name] = $2
    end
  end
  
  remotes
end

#retry_snapshot_push?Boolean

Returns:

  • (Boolean)


325
326
327
328
329
330
331
332
333
334
335
336
# File 'lib/mortar/git.rb', line 325

def retry_snapshot_push?
  @last_snapshot_retry_sleep_time ||= 0
  @snapshot_retry_sleep_time ||= 1

  sleep(@snapshot_retry_sleep_time)
  @last_snapshot_retry_sleep_time, @snapshot_retry_sleep_time = 
    @snapshot_retry_sleep_time, @last_snapshot_retry_sleep_time + @snapshot_retry_sleep_time

  @snapshot_push_attempts ||= 0
  @snapshot_push_attempts += 1
  @snapshot_push_attempts < 10
end

#run_cmd(cmd) ⇒ Object



53
54
55
56
57
58
59
60
# File 'lib/mortar/git.rb', line 53

def run_cmd(cmd)
  begin
    output = %x{#{cmd}}
  rescue Exception => e
    output = ""
  end
  return [output, $?.success?]
end

#safe_copy(pathlist, &block) ⇒ Object

Create a safe temporary directory with a given list of filesystem paths (files or dirs) copied into it



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/mortar/git.rb', line 102

def safe_copy(pathlist, &block)
  # Copy code into a temp directory so we don't confuse editors while snapshotting
  curdir = Dir.pwd
  tmpdir = Dir.mktmpdir

  copy_files_to_dir(pathlist, tmpdir)        
  Dir.chdir(tmpdir)

  if block
    yield
    FileUtils.remove_entry_secure(tmpdir)
    Dir.chdir(curdir)
  else
    return tmpdir
  end
end

#set_upstream(remote_branch_name) ⇒ Object



567
568
569
570
571
572
573
# File 'lib/mortar/git.rb', line 567

def set_upstream(remote_branch_name)
  if has_git?(major_version=1, minor_version=8, revision_version=0)
    git("branch --set-upstream-to #{remote_branch_name}")
  else
    git("branch --set-upstream master #{remote_branch_name}")
  end
end

#stash_working_dir(stash_description) ⇒ Object

stash



586
587
588
589
# File 'lib/mortar/git.rb', line 586

def stash_working_dir(stash_description)
  stash_output = git("stash save --include-untracked #{stash_description}")
  did_stash_changes? stash_output
end

#statusObject

status



599
600
601
# File 'lib/mortar/git.rb', line 599

def status
  git('status --porcelain')
end

#status_code(status_str) ⇒ Object



617
618
619
# File 'lib/mortar/git.rb', line 617

def status_code(status_str)
  status_str[0,2]
end

#sync_embedded_project(project, branch, git_organization) ⇒ Object



342
343
344
345
346
347
348
349
350
351
352
353
354
355
# File 'lib/mortar/git.rb', line 342

def sync_embedded_project(project, branch, git_organization)
  # the project is not a git repo, so we manage a mirror directory that is a git repo
  # branch is which branch to sync to. this will be master if the cloud repo
  # is being initialized, or a branch based on the user's name in any other circumstance
  project_dir = project.root_path
  mirror_dir = "#{mortar_mirrors_dir}/#{project.name}"

  ensure_embedded_project_mirror_exists(mirror_dir, git_organization)
  sync_embedded_project_with_mirror(mirror_dir, project_dir, branch)
  git_ref = sync_embedded_project_mirror_with_cloud(mirror_dir, branch)

  Dir.chdir(project_dir)
  return git_ref
end

#sync_embedded_project_mirror_with_cloud(mirror_dir, branch) ⇒ Object



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

def sync_embedded_project_mirror_with_cloud(mirror_dir, branch)
  # checkout snapshot branch.
  # it will permenantly keep the code in this state (as opposed to the user's base branch, which will be updated)
  Dir.chdir(mirror_dir)
  snapshot_branch = "mortar-snapshot-#{Mortar::UUID.create_random.to_s}"
  git("checkout -b #{snapshot_branch}")

  # push base branch and snapshot branch
  push_with_retry("mortar", branch, "Sending code base branch to Mortar")
  git_ref = push_with_retry("mortar", snapshot_branch, "Sending code snapshot to Mortar")

  git("checkout #{branch}")
  return git_ref
end

#sync_embedded_project_with_mirror(mirror_dir, project_dir, local_branch) ⇒ Object



396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
# File 'lib/mortar/git.rb', line 396

def sync_embedded_project_with_mirror(mirror_dir, project_dir, local_branch)
  # pull from remote branch and overwrite everything, if it exists.
  # if it doesn't exist, create it.
  Dir.chdir(mirror_dir)

  # stash any local changes
  # so we can change branches w/ impunity
  stash_working_dir("cleaning out mirror working directory")

  # fetch remotes
  git("fetch --all")

  remote_branch = "mortar/#{local_branch}"
  if branches.include?(local_branch)
    # if the local branch already exists, use that
    git("checkout #{local_branch}")

    # if a remote branch exists, hard reset the local branch to that
    # to avoid push conflicts
    if all_branches.include?("remotes/#{remote_branch}")
      git("reset --hard #{remote_branch}")
    end
  else
    # start a new local branch off of master
    git("checkout master")

    # if a remote branch exists, checkout the local to track the remote
    if all_branches.include?("remotes/#{remote_branch}")
      # track the remote branch
      git("checkout -b #{local_branch} #{remote_branch}")
    else
      # start a new branch, nothing to track
      git("checkout -b #{local_branch}")
    end
  end

  # wipe mirror dir and copy project files into it
  # since we fetched mortar/master earlier, the git diff will now be b/tw master and the current state
  # mortar_manifest_pathlist(false) means don't copy .git
  FileUtils.rm_rf(Dir.glob("#{mirror_dir}/*"))
  Dir.chdir(project_dir)
  copy_files_to_dir(mortar_manifest_pathlist(false), mirror_dir)

  # update remote branch
  Dir.chdir(mirror_dir)
  unless is_clean_working_directory?
    git("add .")
    git("add -u .") # this gets deletes
    git("commit -m \"mortar development snapshot commit\"")
  end
end

#untracked_filesObject



625
626
627
# File 'lib/mortar/git.rb', line 625

def untracked_files
  git("ls-files -o --exclude-standard").split("\n")
end